├── .gitignore ├── ChangeLog.md ├── README.md ├── build.gradle ├── errorhandler-matchers ├── README.md └── retrofit-rx-matcher │ ├── .gradle │ └── 2.13 │ │ └── taskArtifacts │ │ ├── cache.properties │ │ ├── cache.properties.lock │ │ ├── fileHashes.bin │ │ ├── fileSnapshots.bin │ │ └── taskArtifacts.bin │ ├── build.gradle │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ ├── main │ └── java │ │ └── com │ │ └── workable │ │ └── errorhandler │ │ └── matchers │ │ └── retrofit │ │ ├── Range.java │ │ └── RetrofitMatcherFactory.java │ └── test │ └── java │ └── com │ └── workable │ └── errorhandler │ └── matchers │ └── retrofit │ ├── RangeTest.java │ ├── RetrofitHelper.java │ └── RetrofitMatcherFactoryTest.java ├── errorhandler ├── .gitignore ├── .travis.yml ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── workable │ │ └── errorhandler │ │ ├── Action.java │ │ ├── ActionEntry.java │ │ ├── BlockExecutor.java │ │ ├── ErrorHandler.java │ │ ├── ExceptionMatcher.java │ │ ├── Matcher.java │ │ ├── MatcherFactory.java │ │ └── UnknownErrorCodeException.java │ └── test │ └── java │ └── com │ └── workable │ └── errorhandler │ ├── BarException.java │ ├── DBErrorException.java │ ├── ErrorHandlerTest.java │ ├── FooException.java │ └── QuxException.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.gradle 3 | /build 4 | /dist 5 | /local.properties 6 | *.class 7 | errorhandler-matchers/retrofit-rx-matcher/build/ 8 | errorhandler-matchers/retrofit-rx-matcher/dist/ 9 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | ## v1.0.0 4 | 5 | ### New 6 | 7 | - `errorHandler.run(BlockExecutor)` saves you from a `try/catch` block or two 8 | ```java 9 | try { 10 | doSomething(); 11 | } catch(Exception ex) { 12 | errorHandler.handle(ex); 13 | } 14 | 15 | // can now be written as 16 | 17 | errorHandler.run(() -> doSomething()) 18 | ``` 19 | 20 | ### Breaking 21 | 22 | - `bindErrorCode` renamed to `bind` 23 | - `bindErrorCodeClass` renamed to `bindClass` 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ErrorHandler 2 | [![Download](https://api.bintray.com/packages/workable/maven/ErrorHandler/images/download.svg) ](https://bintray.com/workable/maven/ErrorHandler/_latestVersion) 3 | [![Travis](https://travis-ci.org/Workable/java-error-handler.svg?branch=master)](https://travis-ci.org/Workable/java-error-handler) 4 | 5 | > Error handling library for Android and Java 6 | 7 | Encapsulate error handling logic into objects that adhere to configurable defaults. Then pass them around as parameters or inject them via DI. 8 | 9 | ## Download 10 | Download the [latest JAR](https://bintray.com/workable/maven/ErrorHandler/_latestVersion) or grab via Maven: 11 | ```xml 12 | 13 | com.workable 14 | error-handler 15 | 1.1.0 16 | pom 17 | 18 | ``` 19 | 20 | or Gradle: 21 | 22 | ```groovy 23 | compile 'com.workable:error-handler:1.1.0' 24 | ``` 25 | 26 | 27 | ## Usage 28 | 29 | Let's say we're building a messaging Android app that uses both the network and a local database. 30 | 31 | We need to: 32 | 33 | ### Setup a default ErrorHandler once 34 | 35 | - Configure the default ErrorHandler 36 | - Alias errors to codes that are easier to use like Integer, String and Enum values 37 | - Map errors to actions to take when those errors occur (exceptions thrown) 38 | 39 | ```java 40 | // somewhere inside MessagingApp.java 41 | 42 | ErrorHandler 43 | .defaultErrorHandler() 44 | 45 | // Bind certain exceptions to "offline" 46 | .bind("offline", errorCode -> throwable -> { 47 | return throwable instanceof UnknownHostException || throwable instanceof ConnectException; 48 | }) 49 | 50 | // Bind HTTP 404 status to 404 51 | .bind(404, errorCode -> throwable -> { 52 | return throwable instanceof HttpException && ((HttpException) throwable).code() == 404; 53 | }) 54 | 55 | // Bind HTTP 500 status to 500 56 | .bind(500, errorCode -> throwable -> { 57 | return throwable instanceof HttpException && ((HttpException) throwable).code() == 500; 58 | }) 59 | 60 | // Bind all DB errors to a custom enumeration 61 | .bindClass(DBError.class, errorCode -> throwable -> { 62 | return DBError.from(throwable) == errorCode; 63 | }) 64 | 65 | // Handle HTTP 500 errors 66 | .on(500, (throwable, errorHandler) -> { 67 | displayAlert("Kaboom!"); 68 | }) 69 | 70 | // Handle HTTP 404 errors 71 | .on(404, (throwable, errorHandler) -> { 72 | displayAlert("Not found!"); 73 | }) 74 | 75 | // Handle "offline" errors 76 | .on("offline", (throwable, errorHandler) -> { 77 | displayAlert("Network dead!"); 78 | }) 79 | 80 | // Handle unknown errors 81 | .otherwise((throwable, errorHandler) -> { 82 | displayAlert("Oooops?!"); 83 | }) 84 | 85 | // Always log to a crash/error reporting service 86 | .always((throwable, errorHandler) -> { 87 | Logger.log(throwable); 88 | }); 89 | ``` 90 | 91 | ### Use ErrorHandler inside catch blocks 92 | 93 | ```java 94 | // ErrorHandler instances created using ErrorHandler#create(), delegate to the default ErrorHandler 95 | // So it's actually a "handle the error using only defaults" 96 | // i.e. somewhere inside MessageListActivity.java 97 | try { 98 | fetchNewMessages(); 99 | } catch (Exception ex) { 100 | ErrorHandler.create().handle(ex); 101 | } 102 | ``` 103 | 104 | ### Run blocks of code using ErrorHandler.run 105 | 106 | ```java 107 | ErrorHandler.run(() -> fetchNewMessages()); 108 | ``` 109 | 110 | ### Override defaults when needed 111 | 112 | ```java 113 | // Configure a new ErrorHandler instance that delegates to the default one, for a specific method call 114 | // i.e. somewhere inside MessageListActivity.java 115 | try { 116 | fetchNewMessages(); 117 | } catch (Exception ex) { 118 | ErrorHandler 119 | .create() 120 | .on(StaleDataException.class, (throwable, errorHandler) -> { 121 | reloadList(); 122 | errorHandler.skipDefaults(); 123 | }) 124 | .on(404, (throwable, errorHandler) -> { 125 | // We handle 404 specifically on this screen by overriding the default action 126 | displayAlert("Could not load new messages"); 127 | errorHandler.skipDefaults(); 128 | }) 129 | .on(DBError.READ_ONLY, (throwable, errorHandler) -> { 130 | // We could not open our database to write the new messages 131 | ScheduledJob.saveMessages(someMessages).execute(); 132 | // We also don't want to log this error because ... 133 | errorHandler.skipAlways(); 134 | }) 135 | .handle(ex); 136 | } 137 | ``` 138 | 139 | ### Things to know 140 | 141 | ErrorHandler is __thread-safe__. 142 | 143 | 144 | ## API 145 | 146 | ### Initialize 147 | 148 | * `defaultErrorHandler()` Get the default ErrorHandler. 149 | 150 | * `create()` Create a new ErrorHandler that is linked to the default one. 151 | 152 | * `createIsolated()` Create a new empty ErrorHandler that is not linked to the default one. 153 | 154 | ### Configure 155 | 156 | * `on(Matcher, Action)` Register an _Action_ to be executed if _Matcher_ matches the error. 157 | 158 | * `on(Class, Action)` Register an _Action_ to be executed if error is an instance of `Exception`. 159 | 160 | * `on(T, Action)` Register an _Action_ to be executed if error is bound to T, through `bind()` or `bindClass()`. 161 | 162 | * `otherwise(Action)` Register an _Action_ to be executed only if no other _Action_ gets executed. 163 | 164 | * `always(Action)` Register an _Action_ to be executed always and after all other actions. Works like a `finally` clause. 165 | 166 | * `skipFollowing()` Skip the execution of any subsequent _Actions_ except those registered via `always()`. 167 | 168 | * `skipAlways()` Skip all _Actions_ registered via `always()`. 169 | 170 | * `skipDefaults()` Skip any default actions. Meaning any actions registered on the `defaultErrorHandler` instance. 171 | 172 | * `bind(T, MatcherFactory)` Bind instances of _T_ to match errors through a matcher provided by _MatcherFactory_. 173 | 174 | * `bindClass(Class, MatcherFactory)` Bind class _T_ to match errors through a matcher provided by _MatcherFactory_. 175 | 176 | * `clear()` Clear all registered _Actions_. 177 | 178 | ### Execute 179 | 180 | * `handle(Throwable)` Handle the given error. 181 | 182 | 183 | ## About 184 | 185 | When designing for errors, we usually need to: 186 | 187 | 1. have a **default** handler for every **expected** error 188 | // i.e. network, subscription errors 189 | 2. handle **specific** errors **as appropriate** based on where and when they occur 190 | // i.e. network error while uploading a file, invalid login 191 | 3. have a **catch-all** handler for **unknown** errors 192 | // i.e. system libraries runtime errors we don't anticipate 193 | 4. keep our code **DRY** 194 | 195 | Java, as a language, provides you with a way to do the above. By mapping cross-cutting errors to runtime exceptions and catching them lower in the call stack, while having specific expected errors mapped to checked exceptions and handle them near where the error occurred. Still, countless are the projects where this simple strategy has gone astray with lots of errors being either swallowed or left for the catch-all `Thread.UncaughtExceptionHandler`. Moreover, it usually comes with significant boilerplate code. `ErrorHandler` however eases this practice through its fluent API, error aliases and defaults mechanism. 196 | 197 | This library doesn't try to solve Java specific problems, although it does help with the `log and shallow` anti-pattern as it provides an opinionated and straightforward way to act inside every `catch` block. It was created for the needs of an Android app and proved itself useful very quickly. So it may work for you as well. If you like the concept and you're developing in _Swift_ or _Javascript_, we're baking 'em and will be available really soon. 198 | 199 | ## License 200 | ``` 201 | The MIT License 202 | 203 | Copyright (c) 2013-2016 Workable SA 204 | 205 | Permission is hereby granted, free of charge, to any person obtaining a copy 206 | of this software and associated documentation files (the "Software"), to deal 207 | in the Software without restriction, including without limitation the rights 208 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 209 | copies of the Software, and to permit persons to whom the Software is 210 | furnished to do so, subject to the following conditions: 211 | 212 | The above copyright notice and this permission notice shall be included in 213 | all copies or substantial portions of the Software. 214 | 215 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 216 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 217 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 218 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 219 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 220 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 221 | THE SOFTWARE. 222 | ``` 223 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.workable' 2 | version '1.1.0' 3 | 4 | apply plugin: 'java' 5 | apply plugin: 'maven-publish' 6 | 7 | sourceCompatibility = 1.7 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | testCompile group: 'junit', name: 'junit', version: '4.11' 15 | } 16 | 17 | publishing { 18 | publications { 19 | mavenJava(MavenPublication) { 20 | groupId 'com.workable' 21 | artifactId 'parent' 22 | version '1.1.0' 23 | } 24 | } 25 | 26 | repositories { 27 | maven { 28 | url "./dist" 29 | } 30 | } 31 | } 32 | 33 | 34 | Properties localProps = new Properties() 35 | 36 | try { 37 | localProps.load(project.file('local.properties').newDataInputStream()) 38 | } catch(Exception ex) { 39 | logger.warn('local.properties file is missing') 40 | } -------------------------------------------------------------------------------- /errorhandler-matchers/README.md: -------------------------------------------------------------------------------- 1 | # ErrorHandler Matchers 2 | 3 | By default ErrorHandler provides you with a public, fully customizable MatcherFactory interface. The child modules contained in this folder are officially supported matchers for well known Networking libraries. 4 | 5 | In order to use them, provide an instance of your desired MatcherFactory when building your ErrorHandler instances. 6 | 7 | ## Usage 8 | 9 | ### Retrofit-Rx-Matcher 10 | 11 | ```gradle 12 | compile 'com.workable:retrofit-rx-matcher:1.1.0' 13 | ``` 14 | 15 | ```java 16 | 17 | ErrorHandler 18 | .create() 19 | .bind(400, RetrofitMatcherFactory.create()) 20 | .on(400, (throwable, errorHandler) -> showErrorMessage("what?")) 21 | .handle(httpException); 22 | 23 | // Or bind all integers to Retrofit errors 24 | 25 | ErrorHandler 26 | .create() 27 | .bindClass(Range.class, RetrofitMatcherFactory.createRange()) 28 | .bindClass(Integer.class, RetrofitMatcherFactory.create()) 29 | .on(400, (throwable, errorHandler) -> showErrorMessage("what?")) 30 | .on(Range.of(500, 599), (throwable, errorHandler) -> showErrorMessage("kaboom")) 31 | .handle(httpException); 32 | 33 | ``` 34 | -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/.gradle/2.13/taskArtifacts/cache.properties: -------------------------------------------------------------------------------- 1 | #Thu Sep 08 10:21:58 EEST 2016 2 | -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/.gradle/2.13/taskArtifacts/cache.properties.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Workable/java-error-handler/1c16c8f487593eddd88b55f31e9c9bb8243522df/errorhandler-matchers/retrofit-rx-matcher/.gradle/2.13/taskArtifacts/cache.properties.lock -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/.gradle/2.13/taskArtifacts/fileHashes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Workable/java-error-handler/1c16c8f487593eddd88b55f31e9c9bb8243522df/errorhandler-matchers/retrofit-rx-matcher/.gradle/2.13/taskArtifacts/fileHashes.bin -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/.gradle/2.13/taskArtifacts/fileSnapshots.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Workable/java-error-handler/1c16c8f487593eddd88b55f31e9c9bb8243522df/errorhandler-matchers/retrofit-rx-matcher/.gradle/2.13/taskArtifacts/fileSnapshots.bin -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/.gradle/2.13/taskArtifacts/taskArtifacts.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Workable/java-error-handler/1c16c8f487593eddd88b55f31e9c9bb8243522df/errorhandler-matchers/retrofit-rx-matcher/.gradle/2.13/taskArtifacts/taskArtifacts.bin -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | } 6 | 7 | plugins { 8 | id "com.jfrog.bintray" version "1.7" 9 | } 10 | 11 | group 'com.workable' 12 | version '1.1.0' 13 | 14 | apply plugin: 'java' 15 | apply plugin: 'maven-publish' 16 | 17 | sourceCompatibility = 1.7 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | 23 | dependencies { 24 | testCompile group: 'junit', name: 'junit', version: '4.12' 25 | testCompile 'org.mockito:mockito-core:1.10.19' 26 | 27 | compile project(':errorhandler') 28 | compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' 29 | } 30 | 31 | task sourceJar(type: Jar) { 32 | from sourceSets.main.allJava 33 | } 34 | 35 | task javadocJar(type: Jar) { 36 | classifier = 'javadoc' 37 | from javadoc 38 | } 39 | 40 | publishing { 41 | publications { 42 | mavenJava(MavenPublication) { 43 | groupId 'com.workable' 44 | artifactId 'retrofit-rx-matcher' 45 | version '1.1.0' 46 | 47 | from components.java 48 | 49 | artifact sourceJar { 50 | classifier "sources" 51 | } 52 | 53 | artifact javadocJar { 54 | classifier "javadoc" 55 | } 56 | } 57 | } 58 | 59 | repositories { 60 | maven { 61 | url "./dist" 62 | } 63 | } 64 | } 65 | 66 | 67 | Properties localProps = new Properties() 68 | 69 | try { 70 | localProps.load(project.file('../../local.properties').newDataInputStream()) 71 | } catch(Exception ex) { 72 | logger.warn('local.properties file is missing') 73 | } 74 | 75 | bintray { 76 | user = localProps.getProperty('bintrayUser') 77 | key = localProps.getProperty('bintrayApiKey') 78 | publications = ['mavenJava'] 79 | pkg { 80 | repo = 'maven' 81 | name = 'ErrorHandler' 82 | desc = 'Error handling library for Android and Java' 83 | userOrg = "workable" 84 | licenses = ['MIT'] 85 | vcsUrl = 'https://github.com/Workable/java-error-handler' 86 | labels = ['java', 'error handler', 'errors', 'android'] 87 | publicDownloadNumbers = true 88 | version { 89 | name = '1.1.0' 90 | 91 | desc = 'Error handling library for Android and Java' 92 | vcsTag = 'v1.1.0' 93 | gpg { 94 | sign = true //Determines whether to GPG sign the files. The default is false 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Workable/java-error-handler/1c16c8f487593eddd88b55f31e9c9bb8243522df/errorhandler-matchers/retrofit-rx-matcher/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Sep 08 10:21:58 EEST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip 7 | -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'retrofit-rx-matcher' 2 | include ':errorhandler' -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/src/main/java/com/workable/errorhandler/matchers/retrofit/Range.java: -------------------------------------------------------------------------------- 1 | package com.workable.errorhandler.matchers.retrofit; 2 | 3 | /** 4 | * Range class for HTTP status codes 5 | */ 6 | public class Range { 7 | 8 | private int lowerBound; 9 | private int upperBound; 10 | 11 | /** 12 | * Creates a Range object with lower and upper bound 13 | * @param lowerBound lower limit of Range 14 | * @param upperBound upper limit of Range 15 | * 16 | * @return a Range instance 17 | */ 18 | public static Range of(int lowerBound, int upperBound) { 19 | return new Range(lowerBound, upperBound); 20 | } 21 | 22 | private Range(int lowerBound, int upperBound) { 23 | this.lowerBound = lowerBound; 24 | this.upperBound = upperBound; 25 | } 26 | 27 | /** 28 | * Checks if the passed httpStatusCode is contained in given range 29 | * 30 | * @param httpStatusCode the status code to check 31 | * @return true if contains, otherwise false 32 | */ 33 | public boolean contains(int httpStatusCode) { 34 | return httpStatusCode >= lowerBound && httpStatusCode <= upperBound; 35 | } 36 | 37 | public int getLowerBound() { 38 | return lowerBound; 39 | } 40 | 41 | public int getUpperBound() { 42 | return upperBound; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (this == o) return true; 48 | if (o == null || getClass() != o.getClass()) return false; 49 | 50 | Range range = (Range) o; 51 | 52 | if (lowerBound != range.lowerBound) return false; 53 | return upperBound == range.upperBound; 54 | 55 | } 56 | 57 | @Override 58 | public int hashCode() { 59 | int result = lowerBound; 60 | result = 31 * result + upperBound; 61 | return result; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/src/main/java/com/workable/errorhandler/matchers/retrofit/RetrofitMatcherFactory.java: -------------------------------------------------------------------------------- 1 | package com.workable.errorhandler.matchers.retrofit; 2 | 3 | import com.workable.errorhandler.Matcher; 4 | import com.workable.errorhandler.MatcherFactory; 5 | import retrofit2.adapter.rxjava.HttpException; 6 | 7 | 8 | 9 | public class RetrofitMatcherFactory { 10 | 11 | private RetrofitMatcherFactory() { 12 | // no instances 13 | } 14 | 15 | /** 16 | * Creates a {@link MatcherFactory} that checks HTTP statuses 17 | * 18 | * @return new MatcherFactory for Retrofit Rx HttpException that works with Integer 19 | */ 20 | public static MatcherFactory create() { 21 | return new MatcherFactory() { 22 | public Matcher build(final Integer httpStatusCode) { 23 | return new Matcher() { 24 | public boolean matches(Throwable throwable) { 25 | return throwable instanceof HttpException && 26 | ((HttpException) throwable).code() == httpStatusCode; 27 | } 28 | }; 29 | } 30 | }; 31 | } 32 | 33 | /** 34 | * Creates a {@link MatcherFactory} that checks if HTTP status is in given {@link Range} 35 | * 36 | * @return new MatcherFactory for Retrofit Rx HttpException that works with Range 37 | */ 38 | public static MatcherFactory createRange() { 39 | return new MatcherFactory() { 40 | public Matcher build(final Range range) { 41 | return new Matcher() { 42 | public boolean matches(Throwable throwable) { 43 | return throwable instanceof HttpException && 44 | range.contains(((HttpException)throwable).code()); 45 | } 46 | }; 47 | } 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/src/test/java/com/workable/errorhandler/matchers/retrofit/RangeTest.java: -------------------------------------------------------------------------------- 1 | package com.workable.errorhandler.matchers.retrofit; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class RangeTest { 7 | 8 | @Test 9 | public void test_check_down_bound() { 10 | Range range = Range.of(400, 500); 11 | 12 | Assert.assertTrue(range.contains(400)); 13 | Assert.assertFalse(range.contains(399)); 14 | } 15 | 16 | @Test 17 | public void test_check_upper_bound() { 18 | Range range = Range.of(400, 500); 19 | 20 | Assert.assertTrue(range.contains(500)); 21 | Assert.assertFalse(range.contains(501)); 22 | } 23 | 24 | @Test 25 | public void test_is_in_range() { 26 | Range range = Range.of(400, 500); 27 | 28 | Assert.assertTrue(range.contains(450)); 29 | } 30 | 31 | @Test 32 | public void test_same_range() { 33 | Range range = Range.of(400, 400); 34 | 35 | Assert.assertTrue(range.contains(400)); 36 | Assert.assertFalse(range.contains(500)); 37 | } 38 | 39 | @Test 40 | public void test_range_equality() { 41 | Range range = Range.of(400, 500); 42 | 43 | 44 | Assert.assertTrue(range.equals(Range.of(400, 500))); 45 | Assert.assertFalse(range.equals(Range.of(401, 500))); 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/src/test/java/com/workable/errorhandler/matchers/retrofit/RetrofitHelper.java: -------------------------------------------------------------------------------- 1 | package com.workable.errorhandler.matchers.retrofit; 2 | 3 | import okhttp3.*; 4 | import retrofit2.Response; 5 | 6 | public class RetrofitHelper { 7 | 8 | private RetrofitHelper () { 9 | } 10 | 11 | public static okhttp3.Response generateMockResponseWith(int networkCode) { 12 | okhttp3.Response response = new okhttp3.Response.Builder() 13 | .code(networkCode) 14 | .message("OK") 15 | .body(convertStringResponseBody("MOCK")) 16 | .protocol(Protocol.HTTP_1_1) 17 | .request(new Request.Builder().url("http://localhost/").build()) 18 | .build(); 19 | 20 | return response; 21 | } 22 | 23 | public static Response generateSuccessResponseWith(int networkCode) { 24 | return Response.success(null, generateMockResponseWith(networkCode)); 25 | } 26 | 27 | public static Response generateErrorResponseWith(int networkCode) { 28 | return Response.error(networkCode, generateMockResponseWith(networkCode).body()); 29 | } 30 | 31 | public static ResponseBody convertStringResponseBody(String value) { 32 | return ResponseBody.create(MediaType.parse("text/plain"), value); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /errorhandler-matchers/retrofit-rx-matcher/src/test/java/com/workable/errorhandler/matchers/retrofit/RetrofitMatcherFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.workable.errorhandler.matchers.retrofit; 2 | 3 | 4 | import com.workable.errorhandler.Action; 5 | import com.workable.errorhandler.ErrorHandler; 6 | import junit.framework.TestCase; 7 | import org.junit.Test; 8 | import retrofit2.adapter.rxjava.HttpException; 9 | import org.mockito.Mockito; 10 | 11 | import static org.mockito.Mockito.mock; 12 | import static org.mockito.Mockito.times; 13 | 14 | public class RetrofitMatcherFactoryTest extends TestCase { 15 | 16 | interface ActionDelegate { 17 | void action1(); 18 | } 19 | 20 | private ActionDelegate actionDelegateMock; 21 | 22 | protected void setUp() throws Exception { 23 | actionDelegateMock = mock(ActionDelegate.class); 24 | } 25 | 26 | @Test 27 | public void test_catching_exact_http_code() { 28 | ErrorHandler 29 | .createIsolated() 30 | .bind(400, RetrofitMatcherFactory.create()) 31 | .bindClass(Range.class, RetrofitMatcherFactory.createRange()) 32 | .on(400, new Action() { 33 | @Override 34 | public void execute(Throwable throwable, ErrorHandler errorHandler) { 35 | actionDelegateMock.action1(); 36 | } 37 | }) 38 | .on(Range.of(400, 500), new Action() { 39 | @Override 40 | public void execute(Throwable throwable, ErrorHandler errorHandler) { 41 | actionDelegateMock.action1(); 42 | } 43 | }) 44 | .handle(new HttpException(RetrofitHelper.generateErrorResponseWith(400))); 45 | 46 | 47 | Mockito.verify(actionDelegateMock, times(2)).action1(); 48 | } 49 | 50 | @Test 51 | public void test_not_catching_exact_http_code() { 52 | ErrorHandler 53 | .createIsolated() 54 | .bind(400, RetrofitMatcherFactory.create()) 55 | .bindClass(Range.class, RetrofitMatcherFactory.createRange()) 56 | .on(400, new Action() { 57 | @Override 58 | public void execute(Throwable throwable, ErrorHandler errorHandler) { 59 | actionDelegateMock.action1(); 60 | } 61 | }) 62 | .on(Range.of(450, 450), new Action() { 63 | @Override 64 | public void execute(Throwable throwable, ErrorHandler errorHandler) { 65 | actionDelegateMock.action1(); 66 | } 67 | }) 68 | .handle(new HttpException(RetrofitHelper.generateErrorResponseWith(401))); 69 | 70 | 71 | Mockito.verify(actionDelegateMock, times(0)).action1(); 72 | } 73 | 74 | @Test 75 | public void test_catching_with_class() { 76 | ErrorHandler 77 | .createIsolated() 78 | .bindClass(Integer.class, RetrofitMatcherFactory.create()) 79 | .on(500, new Action() { 80 | @Override 81 | public void execute(Throwable throwable, ErrorHandler errorHandler) { 82 | actionDelegateMock.action1(); 83 | } 84 | }) 85 | .handle(new HttpException(RetrofitHelper.generateErrorResponseWith(401))); 86 | 87 | 88 | Mockito.verify(actionDelegateMock, times(0)).action1(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /errorhandler/.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.gradle 3 | /build 4 | /dist 5 | /local.properties 6 | -------------------------------------------------------------------------------- /errorhandler/.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 -------------------------------------------------------------------------------- /errorhandler/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | } 6 | 7 | plugins { 8 | id "com.jfrog.bintray" version "1.7" 9 | } 10 | 11 | group 'com.workable' 12 | version '1.1.0' 13 | 14 | apply plugin: 'java' 15 | apply plugin: 'maven-publish' 16 | 17 | sourceCompatibility = 1.7 18 | targetCompatibility = 1.7 19 | 20 | compileTestJava { 21 | sourceCompatibility = 1.8 22 | targetCompatibility = 1.8 23 | } 24 | 25 | repositories { 26 | mavenCentral() 27 | jcenter() 28 | } 29 | 30 | dependencies { 31 | testCompile group: 'junit', name: 'junit', version: '4.11' 32 | testCompile "org.mockito:mockito-core:1.+" 33 | } 34 | 35 | task sourceJar(type: Jar) { 36 | from sourceSets.main.allJava 37 | } 38 | 39 | task javadocJar(type: Jar) { 40 | classifier = 'javadoc' 41 | from javadoc 42 | } 43 | 44 | publishing { 45 | publications { 46 | mavenJava(MavenPublication) { 47 | groupId 'com.workable' 48 | artifactId 'error-handler' 49 | version '1.1.0' 50 | 51 | from components.java 52 | 53 | artifact sourceJar { 54 | classifier "sources" 55 | } 56 | 57 | artifact javadocJar { 58 | classifier "javadoc" 59 | } 60 | } 61 | } 62 | 63 | repositories { 64 | maven { 65 | url "./dist" 66 | } 67 | } 68 | } 69 | 70 | 71 | Properties localProps = new Properties() 72 | 73 | try { 74 | localProps.load(project.file('../local.properties').newDataInputStream()) 75 | } catch(Exception ex) { 76 | logger.warn('local.properties file is missing') 77 | } 78 | 79 | bintray { 80 | user = localProps.getProperty('bintrayUser') 81 | key = localProps.getProperty('bintrayApiKey') 82 | publications = ['mavenJava'] 83 | pkg { 84 | repo = 'maven' 85 | name = 'ErrorHandler' 86 | desc = 'Error handling library for Android and Java' 87 | userOrg = "workable" 88 | licenses = ['MIT'] 89 | vcsUrl = 'https://github.com/Workable/java-error-handler' 90 | labels = ['java', 'error handler', 'errors', 'android'] 91 | publicDownloadNumbers = true 92 | version { 93 | name = '1.1.0' 94 | 95 | desc = 'Error handling library for Android and Java' 96 | vcsTag = 'v1.1.0' 97 | gpg { 98 | sign = true //Determines whether to GPG sign the files. The default is false 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /errorhandler/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Workable/java-error-handler/1c16c8f487593eddd88b55f31e9c9bb8243522df/errorhandler/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /errorhandler/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jul 15 11:13:10 EEST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-all.zip 7 | -------------------------------------------------------------------------------- /errorhandler/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /errorhandler/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /errorhandler/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'errorhandler' 2 | -------------------------------------------------------------------------------- /errorhandler/src/main/java/com/workable/errorhandler/Action.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2016 Workable SA 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.workable.errorhandler; 26 | 27 | /** 28 | * A functional interface representing an action that gets executed 29 | * upon an error by an {@link ErrorHandler}. 30 | */ 31 | public interface Action { 32 | 33 | /** 34 | * @param throwable The Throwable that was checked. 35 | * @param errorHandler Current Instance of the ErrorHandler. 36 | */ 37 | void execute(Throwable throwable, ErrorHandler errorHandler); 38 | } 39 | -------------------------------------------------------------------------------- /errorhandler/src/main/java/com/workable/errorhandler/ActionEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2016 Workable SA 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.workable.errorhandler; 26 | 27 | /** 28 | * Container to ease passing around a tuple of two objects. This object provides a sensible 29 | * implementation of equals(), returning true if equals() is true on each of the contained 30 | * objects. 31 | */ 32 | public class ActionEntry { 33 | 34 | public final Matcher matcher; 35 | public final Action action; 36 | 37 | /** 38 | * Constructor for an ActionEntry. 39 | * 40 | * @param matcher the matcher object in the ActionEntry 41 | * @param action the action object in the ActionEntry 42 | */ 43 | public ActionEntry(Matcher matcher, Action action) { 44 | this.matcher = matcher; 45 | this.action = action; 46 | } 47 | 48 | @Override 49 | public boolean equals(Object o) { 50 | if (this == o) return true; 51 | if (o == null || getClass() != o.getClass()) return false; 52 | 53 | ActionEntry that = (ActionEntry) o; 54 | 55 | if (!matcher.equals(that.matcher)) return false; 56 | return action.equals(that.action); 57 | } 58 | 59 | /** 60 | * Compute a hash code using the hash codes of the underlying objects 61 | * 62 | * @return a hashcode of the ActionEntry 63 | */ 64 | @Override 65 | public int hashCode() { 66 | return (matcher == null ? 0 : matcher.hashCode()) ^ (action == null ? 0 : action.hashCode()); 67 | } 68 | 69 | /** 70 | * Convenience method for creating an ActionEntry 71 | * 72 | * @param matcher the matcher object in the ActionEntry 73 | * @param action the action object in the ActionEntry 74 | * @return a new ActionEntry 75 | */ 76 | public static ActionEntry from(Matcher matcher, Action action) { 77 | return new ActionEntry(matcher, action); 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /errorhandler/src/main/java/com/workable/errorhandler/BlockExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2016 Workable SA 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.workable.errorhandler; 26 | 27 | /** 28 | * Functional interface to run code blocks 29 | * 30 | * @author Pavlos-Petros Tournaris 31 | */ 32 | public interface BlockExecutor { 33 | void invoke() throws Exception; 34 | } 35 | -------------------------------------------------------------------------------- /errorhandler/src/main/java/com/workable/errorhandler/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2016 Workable SA 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.workable.errorhandler; 26 | 27 | import java.util.ArrayList; 28 | import java.util.HashMap; 29 | import java.util.List; 30 | import java.util.Map; 31 | 32 | 33 | /** 34 | * An ErrorHandler is responsible for handling an error by executing one or more actions, 35 | * instances of {@link Action}, that are found to match the error. 36 | * 37 | * @author Stratos Pavlakis - pavlakis@workable.com 38 | * @author Pavlos-Petros Tournaris - tournaris@workable.com 39 | * @author Vasilis Charalampakis - basilis@workable.com 40 | */ 41 | public class ErrorHandler { 42 | 43 | private static ErrorHandler defaultInstance = null; 44 | 45 | private Map errorCodeMap; 46 | 47 | private List actions; 48 | private List otherwiseActions; 49 | private List alwaysActions; 50 | 51 | private ThreadLocal localContext; 52 | 53 | private ErrorHandler parentErrorHandler; 54 | 55 | /** 56 | * Need a private constructor as we want new instances created 57 | * only via the {@link #create} methods. 58 | */ 59 | private ErrorHandler() { 60 | super(); 61 | this.actions = new ArrayList<>(); 62 | this.otherwiseActions = new ArrayList<>(); 63 | this.alwaysActions = new ArrayList<>(); 64 | this.errorCodeMap = new HashMap<>(); 65 | this.localContext = new ThreadLocal(){ 66 | @Override 67 | protected Context initialValue() { 68 | return new Context(); 69 | } 70 | }; 71 | } 72 | 73 | /** 74 | * Create a new ErrorHandler with the given one as parent. 75 | * 76 | * @param parentErrorHandler the parent @{link ErrorHandler} 77 | */ 78 | private ErrorHandler(ErrorHandler parentErrorHandler) { 79 | this(); 80 | this.parentErrorHandler = parentErrorHandler; 81 | } 82 | 83 | /** 84 | * Create a new @{link ErrorHandler}, isolated from the default one. 85 | *

86 | * In other words, designed to handle all errors by itself without delegating 87 | * to the default error handler. 88 | * 89 | * @return returns a new {@code ErrorHandler} instance 90 | */ 91 | public static ErrorHandler createIsolated() { 92 | return new ErrorHandler(); 93 | } 94 | 95 | /** 96 | * Create a new @{link ErrorHandler}, that delegates to the default one. 97 | *

98 | * Any default actions, are always executed after the ones registered on this one. 99 | * 100 | * @return returns a new {@code ErrorHandler} instance 101 | */ 102 | public static ErrorHandler create() { 103 | return new ErrorHandler(defaultErrorHandler()); 104 | } 105 | 106 | /** 107 | * Get the default @{link ErrorHandler}, a singleton object 108 | * to which all other instances by default delegate to. 109 | * 110 | * @return the default @{link ErrorHandler} instance 111 | */ 112 | public static synchronized ErrorHandler defaultErrorHandler() { 113 | if (defaultInstance == null) { 114 | defaultInstance = new ErrorHandler(); 115 | } 116 | return defaultInstance; 117 | } 118 | 119 | /** 120 | * Register {@code action} to be executed by {@link #handle(Throwable)}, 121 | * if the thrown error matches the {@code matcher}. 122 | * 123 | * @param matcher a matcher to match the thrown error 124 | * @param action the associated action 125 | * @return the current {@code ErrorHandler} instance - to use in command chains 126 | */ 127 | public ErrorHandler on(Matcher matcher, Action action) { 128 | if (matcher == null) { 129 | throw new IllegalArgumentException("matcher cannot be null"); 130 | } 131 | assertNotNullAction(action); 132 | this.actions.add(ActionEntry.from(matcher, action)); 133 | return this; 134 | } 135 | 136 | /** 137 | * Register {@code action} to be executed by {@link #handle(Throwable)}, 138 | * if the thrown error is an instance of {@code exceptionClass}. 139 | * 140 | * @param exceptionClass the class of the error 141 | * @param action the associated action 142 | * @return the current {@code ErrorHandler} instance - to use in command chains 143 | */ 144 | public ErrorHandler on(Class exceptionClass, Action action) { 145 | if (exceptionClass == null) { 146 | throw new IllegalArgumentException("exceptionClass cannot be null"); 147 | } 148 | assertNotNullAction(action); 149 | actions.add(ActionEntry.from(new ExceptionMatcher(exceptionClass), action)); 150 | return this; 151 | } 152 | 153 | /** 154 | * Register {@code action} to be executed by {@link #handle(Throwable)}, 155 | * if the thrown error is bound (associated) to {@code errorCode}. 156 | *

157 | * See {@link #bindClass(Class, MatcherFactory)} and {@link #bind(Object, MatcherFactory)} 158 | * on how to associate arbitrary error codes with actual Throwables via {@link Matcher}. 159 | *

160 | * @param the error code type 161 | * @param errorCode the error code 162 | * @param action the associated action 163 | * @return the current {@code ErrorHandler} instance - to use in command chains 164 | */ 165 | public ErrorHandler on(T errorCode, Action action) { 166 | if (errorCode == null) { 167 | throw new IllegalArgumentException("errorCode cannot be null"); 168 | } 169 | 170 | MatcherFactory matcherFactory = getMatcherFactoryForErrorCode(errorCode); 171 | if (matcherFactory == null) { 172 | throw new UnknownErrorCodeException(errorCode); 173 | } 174 | 175 | actions.add(ActionEntry.from(matcherFactory.build(errorCode), action)); 176 | return this; 177 | } 178 | 179 | /** 180 | * Register {@code action} to be executed in case no other conditional 181 | * action gets executed. 182 | * 183 | * @param action the action 184 | * @return the current {@code ErrorHandler} instance - to use in command chains 185 | */ 186 | public ErrorHandler otherwise(Action action) { 187 | assertNotNullAction(action); 188 | otherwiseActions.add(action); 189 | return this; 190 | } 191 | 192 | /** 193 | * Register {@code action} to be executed on all errors. 194 | * 195 | * @param action the action 196 | * @return the current {@code ErrorHandler} instance - to use in command chains 197 | */ 198 | public ErrorHandler always(Action action) { 199 | assertNotNullAction(action); 200 | alwaysActions.add(action); 201 | return this; 202 | } 203 | 204 | /** 205 | * Skip all following actions registered via an {@code on} method 206 | * @return the current {@code ErrorHandler} instance - to use in command chains 207 | */ 208 | public ErrorHandler skipFollowing() { 209 | if (localContext != null) { 210 | localContext.get().skipFollowing = true; 211 | } 212 | return this; 213 | } 214 | 215 | /** 216 | * Skip all actions registered via {@link #always(Action)} 217 | * @return the current {@code ErrorHandler} instance - to use in command chains 218 | */ 219 | public ErrorHandler skipAlways() { 220 | if (localContext != null) { 221 | localContext.get().skipAlways = true; 222 | } 223 | return this; 224 | } 225 | 226 | /** 227 | * Skip the default matching actions if any 228 | * @return the current {@code ErrorHandler} instance - to use in command chains 229 | */ 230 | public ErrorHandler skipDefaults() { 231 | if (localContext != null) { 232 | localContext.get().skipDefaults = true; 233 | } 234 | return this; 235 | } 236 | 237 | protected void handle(Throwable error, ThreadLocal context) { 238 | if (error == null) 239 | throw new IllegalArgumentException("error to be checked can not be null"); 240 | 241 | localContext = context; 242 | 243 | Context ctx = localContext.get(); 244 | 245 | for (ActionEntry actionEntry : actions) { 246 | if (ctx.skipFollowing) break; 247 | if (actionEntry.matcher.matches(error)) { 248 | actionEntry.action.execute(error, this); 249 | ctx.handled = true; 250 | } 251 | } 252 | 253 | if (!ctx.handled && !otherwiseActions.isEmpty()) { 254 | for (Action action : otherwiseActions) { 255 | action.execute(error, this); 256 | ctx.handled = true; 257 | } 258 | } 259 | 260 | if (!ctx.skipAlways) { 261 | for (Action action : alwaysActions) { 262 | action.execute(error, this); 263 | ctx.handled = true; 264 | } 265 | } 266 | 267 | if (parentErrorHandler != null && !ctx.skipDefaults) { 268 | parentErrorHandler.handle(error, localContext); 269 | } 270 | } 271 | 272 | /** 273 | * Run a custom code block and assign current ErrorHandler instance 274 | * to handle a possible exception throw in 'catch'. 275 | * 276 | * @param blockExecutor functional interface containing Exception prone code 277 | */ 278 | public void run(BlockExecutor blockExecutor) { 279 | try { 280 | blockExecutor.invoke(); 281 | } catch (Exception exception) { 282 | handle(exception, localContext); 283 | } 284 | } 285 | 286 | /** 287 | * Handle {@code error} by executing all matching actions. 288 | * 289 | * @param error the error as a {@link Throwable} 290 | */ 291 | public void handle(Throwable error) { 292 | this.handle(error, localContext); 293 | } 294 | 295 | /** 296 | * Bind an {@code errorCode} to a {@code Matcher}, using a {@code MatcherFactory}. 297 | * 298 | *

299 | * For example, when we need to catch a network timeout it's better to just write "timeout" 300 | * instead of a train-wreck expression. So we need to bind this "timeout" error code to an actual 301 | * condition that will check the actual error when it occurs to see if its a network timeout or not. 302 | *

303 | * 304 | *
305 |      * {@code
306 |      *   ErrorHandler
307 |      *      .defaultErrorHandler()
308 |      *      .bind("timeout", errorCode -> throwable -> {
309 |      *          return (throwable instanceof SocketTimeoutException) && throwable.getMessage().contains("Read timed out");
310 |      *       });
311 |      *
312 |      *   // ...
313 |      *
314 |      *   ErrorHandler
315 |      *      .build()
316 |      *      .on("timeout", (throwable, handler) -> {
317 |      *          showOfflineScreen();
318 |      *      })
319 |      * }
320 |      * 
321 | * 322 | * 323 | * @param the error code type 324 | * @param errorCode the errorCode value, can use a primitive for clarity and let it be autoboxed 325 | * @param matcherFactory a factory that given an error code, provides a matcher to match the error against it 326 | * @return the current {@code ErrorHandler} instance - to use in command chains 327 | */ 328 | public ErrorHandler bind(T errorCode, MatcherFactory matcherFactory) { 329 | errorCodeMap.put(new ErrorCodeIdentifier<>(errorCode), matcherFactory); 330 | return this; 331 | } 332 | 333 | /** 334 | * Bind an {@code errorCode} Class to a {@code Matcher}, using a {@code MatcherFactory}. 335 | * 336 | *

337 | * For example, when we prefer using plain integers to refer to HTTP errors instead of 338 | * checking the HTTPException status code every time. 339 | *

340 | * 341 | *
342 |      * {@code
343 |      *   ErrorHandler
344 |      *      .defaultErrorHandler()
345 |      *      .bindClass(Integer.class, errorCode -> throwable -> {
346 |      *          return throwable instanceof HTTPException && ((HTTPException)throwable).getStatusCode() == errorCode;
347 |      *       });
348 |      *
349 |      *   // ...
350 |      *
351 |      *   ErrorHandler
352 |      *      .build()
353 |      *      .on(404, (throwable, handler) -> {
354 |      *          showResourceNotFoundError();
355 |      *      })
356 |      *      .on(500, (throwable, handler) -> {
357 |      *          showServerError();
358 |      *      })
359 |      * }
360 |      * 
361 | * 362 | * @param the error code type 363 | * @param errorCodeClass the errorCode class 364 | * @param matcherFactory a factory that given an error code, provides a matcher to match the error against it 365 | * @return the current {@code ErrorHandler} instance - to use in command chains 366 | */ 367 | public ErrorHandler bindClass(Class errorCodeClass, MatcherFactory matcherFactory) { 368 | errorCodeMap.put(new ErrorCodeIdentifier<>(errorCodeClass), matcherFactory); 369 | return this; 370 | } 371 | 372 | @SuppressWarnings("unchecked") 373 | protected MatcherFactory getMatcherFactoryForErrorCode(T errorCode) { 374 | MatcherFactory matcherFactory; 375 | matcherFactory = errorCodeMap.get(new ErrorCodeIdentifier<>(errorCode)); 376 | 377 | if (matcherFactory != null) { 378 | return matcherFactory; 379 | } 380 | 381 | matcherFactory = errorCodeMap.get(new ErrorCodeIdentifier(errorCode.getClass())); 382 | 383 | if (matcherFactory != null) { 384 | return matcherFactory; 385 | } 386 | 387 | if (parentErrorHandler != null) { 388 | return parentErrorHandler.getMatcherFactoryForErrorCode(errorCode); 389 | } 390 | 391 | return null; 392 | } 393 | 394 | /** 395 | * Clear ErrorHandler instance from all its registered Actions and Matchers. 396 | */ 397 | public void clear() { 398 | actions.clear(); 399 | errorCodeMap.clear(); 400 | otherwiseActions.clear(); 401 | alwaysActions.clear(); 402 | if (localContext != null) { 403 | localContext.get().clear(); 404 | } 405 | } 406 | 407 | /** 408 | * Throws if {@code action} is null 409 | * 410 | * @param action the action to assert against 411 | */ 412 | private void assertNotNullAction(Action action) { 413 | if (action == null) { 414 | throw new IllegalArgumentException("action cannot be null"); 415 | } 416 | } 417 | 418 | private static class Context { 419 | private HashMap keys = new HashMap<>(); 420 | 421 | boolean handled; 422 | boolean skipDefaults = false; 423 | boolean skipFollowing = false; 424 | boolean skipAlways = false; 425 | 426 | public Object get(Object key) { 427 | return keys.get(key); 428 | } 429 | 430 | public Object put(String key, Object value) { 431 | return keys.put(key, value); 432 | } 433 | 434 | public Object remove(Object key) { 435 | return keys.remove(key); 436 | } 437 | 438 | void clear() { 439 | keys.clear(); 440 | skipDefaults = false; 441 | skipFollowing = false; 442 | skipAlways = false; 443 | } 444 | } 445 | 446 | /** 447 | * Used to identify an error code either by its "literal" value 448 | * or by its Class. 449 | *

450 | * When using custom objects as error codes, 451 | * make sure you implement {@link Object#equals(Object)} to allow ErrorHandler 452 | * perform equality comparisons between instances. 453 | */ 454 | private static final class ErrorCodeIdentifier { 455 | private T errorCode; 456 | private Class errorCodeClass; 457 | 458 | ErrorCodeIdentifier(T errorCode) { 459 | this.errorCode = errorCode; 460 | } 461 | 462 | ErrorCodeIdentifier(Class errorCodeClass) { 463 | this.errorCodeClass = errorCodeClass; 464 | } 465 | 466 | @Override 467 | public boolean equals(Object o) { 468 | if (this == o) return true; 469 | if (o == null || getClass() != o.getClass()) return false; 470 | 471 | ErrorCodeIdentifier that = (ErrorCodeIdentifier) o; 472 | 473 | if (errorCode != null ? !errorCode.equals(that.errorCode) : that.errorCode != null) 474 | return false; 475 | return errorCodeClass != null ? errorCodeClass.equals(that.errorCodeClass) : that.errorCodeClass == null; 476 | 477 | } 478 | 479 | @Override 480 | public int hashCode() { 481 | int result = errorCode != null ? errorCode.hashCode() : 0; 482 | result = 31 * result + (errorCodeClass != null ? errorCodeClass.hashCode() : 0); 483 | return result; 484 | } 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /errorhandler/src/main/java/com/workable/errorhandler/ExceptionMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2016 Workable SA 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.workable.errorhandler; 26 | 27 | public class ExceptionMatcher implements Matcher { 28 | 29 | private Class errorClass; 30 | 31 | public ExceptionMatcher(Class errorClass) { 32 | this.errorClass = errorClass; 33 | } 34 | 35 | @Override 36 | public boolean matches(Throwable throwable) { 37 | return errorClass.isInstance(throwable); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /errorhandler/src/main/java/com/workable/errorhandler/Matcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2016 Workable SA 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.workable.errorhandler; 26 | 27 | public interface Matcher { 28 | 29 | boolean matches(Throwable throwable); 30 | 31 | } 32 | 33 | 34 | -------------------------------------------------------------------------------- /errorhandler/src/main/java/com/workable/errorhandler/MatcherFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2016 Workable SA 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.workable.errorhandler; 26 | 27 | /** 28 | * A functional interface for {@link Matcher} factories 29 | * 30 | * @author Stratos Pavlakis - pavlakis@workable.com 31 | */ 32 | public interface MatcherFactory { 33 | 34 | /** 35 | * Build a {@link Matcher} to match the given error code against an error 36 | * 37 | * @param errorCode the error code 38 | * @return a new {@link Matcher} 39 | */ 40 | Matcher build(T errorCode); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /errorhandler/src/main/java/com/workable/errorhandler/UnknownErrorCodeException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2016 Workable SA 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.workable.errorhandler; 26 | 27 | /** 28 | * Runtime exception to indicate that the user tried to register 29 | * an {@link Action} via {@link ErrorHandler#on(Object, Action)} with an unknown error code. 30 | * 31 | * @author Stratos Pavlakis - pavlakis@workable.com 32 | */ 33 | public class UnknownErrorCodeException extends RuntimeException { 34 | 35 | private Object errorCode; 36 | 37 | public UnknownErrorCodeException(Object errorCode) { 38 | this.errorCode = errorCode; 39 | } 40 | 41 | public Object getErrorCode() { 42 | return errorCode; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /errorhandler/src/test/java/com/workable/errorhandler/BarException.java: -------------------------------------------------------------------------------- 1 | package com.workable.errorhandler; 2 | 3 | /** 4 | * A runtime test exception for using in {@link ErrorHandler} unit tests 5 | * 6 | * @author Stratos Pavlakis 7 | */ 8 | public class BarException extends RuntimeException { 9 | 10 | private boolean openBar = true; 11 | 12 | public BarException(String message) { 13 | super(message); 14 | } 15 | 16 | public BarException(String message, boolean openBar) { 17 | super(message); 18 | this.openBar = openBar; 19 | } 20 | 21 | public boolean isOpenBar() { 22 | return openBar; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /errorhandler/src/test/java/com/workable/errorhandler/DBErrorException.java: -------------------------------------------------------------------------------- 1 | package com.workable.errorhandler; 2 | 3 | /** 4 | * A runtime test exception for using in {@link ErrorHandler} unit tests 5 | * 6 | * @author Pavlos-Petros Tournaris 7 | */ 8 | public class DBErrorException extends Exception { 9 | 10 | public DBErrorException(String message) { 11 | super(message); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /errorhandler/src/test/java/com/workable/errorhandler/ErrorHandlerTest.java: -------------------------------------------------------------------------------- 1 | package com.workable.errorhandler; 2 | 3 | import junit.framework.TestCase; 4 | import org.junit.Test; 5 | import org.mockito.InOrder; 6 | import org.mockito.Mockito; 7 | 8 | import static org.mockito.Mockito.*; 9 | 10 | /** 11 | * {@link ErrorHandler} unit tests 12 | * 13 | * @author Stratos Pavlakis 14 | */ 15 | public class ErrorHandlerTest extends TestCase { 16 | 17 | interface ActionDelegate { 18 | 19 | void action1(); 20 | 21 | void action2(); 22 | 23 | void action3(); 24 | 25 | void action4(); 26 | 27 | void action5(); 28 | 29 | void otherwise1(); 30 | 31 | void always1(); 32 | 33 | void defaultAction1(); 34 | 35 | void defaultAction2(); 36 | 37 | void defaultAction3(); 38 | 39 | void defaultOtherwise(); 40 | 41 | void defaultAlways(); 42 | } 43 | 44 | private ActionDelegate actionDelegateMock; 45 | 46 | protected void setUp() { 47 | actionDelegateMock = mock(ActionDelegate.class); 48 | 49 | ErrorHandler 50 | .defaultErrorHandler() 51 | .bind("closed:bar", errorCode -> throwable -> { 52 | if (throwable instanceof BarException) { 53 | return !((BarException) throwable).isOpenBar(); 54 | } else { 55 | return false; 56 | } 57 | }) 58 | .bindClass(Integer.class, errorCode -> throwable -> { 59 | if (throwable instanceof QuxException) { 60 | return ((QuxException) throwable).getErrorStatus() == errorCode; 61 | } else { 62 | return false; 63 | } 64 | }) 65 | .on(FooException.class, (throwable, errorHandler) -> actionDelegateMock.defaultAction1()) 66 | .on(500, (throwable, errorHandler) -> actionDelegateMock.defaultAction2()) 67 | .on("closed:bar", (throwable, errorHandler) -> actionDelegateMock.defaultAction3()) 68 | .otherwise((throwable, errorHandler) -> actionDelegateMock.defaultOtherwise()) 69 | .always((throwable, errorHandler) -> actionDelegateMock.defaultAlways()); 70 | } 71 | 72 | protected void tearDown() { 73 | ErrorHandler 74 | .defaultErrorHandler() 75 | .clear(); 76 | } 77 | 78 | @Test 79 | public void testActionsExecutionOrder() { 80 | ErrorHandler errorHandler = ErrorHandler 81 | .create() 82 | .on(FooException.class, (throwable, handler) -> actionDelegateMock.action1()) 83 | .on( 84 | (throwable) -> { 85 | try { 86 | return FooException.class.cast(throwable).isFatal(); 87 | } catch (ClassCastException ignore) { 88 | return false; 89 | } 90 | }, 91 | (throwable, handler) -> actionDelegateMock.action2() 92 | ) 93 | .on("closed:bar", (throwable, handler) -> actionDelegateMock.action3()) 94 | .on(400, (throwable, handler) -> actionDelegateMock.action4()) 95 | .on(500, (throwable, handler) -> actionDelegateMock.action5()) 96 | .otherwise((throwable, handler) -> actionDelegateMock.otherwise1()) 97 | .always((throwable, handler) -> actionDelegateMock.always1()); 98 | 99 | 100 | InOrder testVerifier1 = inOrder(actionDelegateMock); 101 | 102 | errorHandler.handle(new FooException("test1")); 103 | 104 | testVerifier1.verify(actionDelegateMock).action1(); 105 | testVerifier1.verify(actionDelegateMock).always1(); 106 | testVerifier1.verify(actionDelegateMock).defaultAction1(); 107 | testVerifier1.verify(actionDelegateMock).defaultAlways(); 108 | testVerifier1.verifyNoMoreInteractions(); 109 | Mockito.verifyNoMoreInteractions(actionDelegateMock); 110 | 111 | reset(actionDelegateMock); 112 | 113 | InOrder testVerifier2 = inOrder(actionDelegateMock); 114 | 115 | errorHandler.handle(new BarException("What a shame", false)); 116 | 117 | testVerifier2.verify(actionDelegateMock).action3(); 118 | testVerifier2.verify(actionDelegateMock).always1(); 119 | testVerifier2.verify(actionDelegateMock).defaultAction3(); 120 | testVerifier2.verify(actionDelegateMock).defaultAlways(); 121 | testVerifier2.verifyNoMoreInteractions(); 122 | Mockito.verifyNoMoreInteractions(actionDelegateMock); 123 | 124 | reset(actionDelegateMock); 125 | 126 | 127 | InOrder testVerifier3 = inOrder(actionDelegateMock); 128 | 129 | errorHandler.handle(new QuxException(500)); 130 | 131 | testVerifier3.verify(actionDelegateMock).action5(); 132 | testVerifier3.verify(actionDelegateMock).always1(); 133 | testVerifier3.verify(actionDelegateMock).defaultAction2(); 134 | testVerifier3.verify(actionDelegateMock).defaultAlways(); 135 | testVerifier3.verifyNoMoreInteractions(); 136 | Mockito.verifyNoMoreInteractions(actionDelegateMock); 137 | } 138 | 139 | @Test 140 | public void testSkipDefaults() { 141 | ErrorHandler 142 | .create() 143 | .on(FooException.class, (throwable, handler) -> { 144 | actionDelegateMock.action1(); 145 | }) 146 | .handle(new FooException("foo error")); 147 | 148 | Mockito.verify(actionDelegateMock, times(1)).action1(); 149 | Mockito.verify(actionDelegateMock, times(1)).defaultAction1(); 150 | 151 | reset(actionDelegateMock); 152 | 153 | ErrorHandler 154 | .create() 155 | .on(FooException.class, (throwable, handler) -> { 156 | actionDelegateMock.action1(); 157 | handler.skipDefaults(); 158 | }) 159 | .handle(new FooException("foo error")); 160 | 161 | Mockito.verify(actionDelegateMock, times(1)).action1(); 162 | Mockito.verify(actionDelegateMock, never()).defaultAction1(); 163 | } 164 | 165 | @Test 166 | public void testSkipFollowing() { 167 | InOrder testVerifier = inOrder(actionDelegateMock); 168 | 169 | ErrorHandler 170 | .create() 171 | .on(FooException.class, (throwable, handler) -> { 172 | actionDelegateMock.action1(); 173 | }) 174 | .on(FooException.class, (throwable, handler) -> { 175 | actionDelegateMock.action2(); 176 | handler.skipFollowing(); 177 | }) 178 | .on(FooException.class, (throwable, handler) -> { 179 | actionDelegateMock.action3(); 180 | }) 181 | .on(FooException.class, (throwable, handler) -> { 182 | actionDelegateMock.action4(); 183 | }) 184 | .handle(new FooException("foo error")); 185 | 186 | testVerifier.verify(actionDelegateMock).action1(); 187 | testVerifier.verify(actionDelegateMock).action2(); 188 | testVerifier.verify(actionDelegateMock).defaultAlways(); 189 | testVerifier.verifyNoMoreInteractions(); 190 | Mockito.verifyNoMoreInteractions(actionDelegateMock); 191 | } 192 | 193 | @Test 194 | public void testSkipAlways() { 195 | InOrder testVerifier = inOrder(actionDelegateMock); 196 | 197 | ErrorHandler 198 | .create() 199 | .on(FooException.class, (throwable, handler) -> { 200 | actionDelegateMock.action1(); 201 | }) 202 | .on(FooException.class, (throwable, handler) -> { 203 | actionDelegateMock.action2(); 204 | handler.skipAlways(); 205 | }) 206 | .on(FooException.class, (throwable, handler) -> { 207 | actionDelegateMock.action3(); 208 | }) 209 | .on(FooException.class, (throwable, handler) -> { 210 | actionDelegateMock.action4(); 211 | }) 212 | .handle(new FooException("foo error")); 213 | 214 | testVerifier.verify(actionDelegateMock).action1(); 215 | testVerifier.verify(actionDelegateMock).action2(); 216 | testVerifier.verify(actionDelegateMock).action3(); 217 | testVerifier.verify(actionDelegateMock).action4(); 218 | testVerifier.verify(actionDelegateMock).defaultAction1(); 219 | testVerifier.verifyNoMoreInteractions(); 220 | Mockito.verifyNoMoreInteractions(actionDelegateMock); 221 | } 222 | 223 | @Test 224 | public void testEnumClassHandling() { 225 | InOrder testVerifier = inOrder(actionDelegateMock); 226 | 227 | ErrorHandler 228 | .create() 229 | .bindClass(DBError.class, errorCode -> throwable -> { 230 | if (throwable instanceof DBErrorException) { 231 | return DBError.from((DBErrorException) throwable) == errorCode; 232 | } 233 | return false; 234 | }) 235 | .on(DBError.READ_ONLY, (throwable, errorHandler) -> { 236 | actionDelegateMock.action1(); 237 | errorHandler.skipAlways(); 238 | }) 239 | .handle(new DBErrorException("read-only")); 240 | 241 | testVerifier.verify(actionDelegateMock).action1(); 242 | testVerifier.verifyNoMoreInteractions(); 243 | Mockito.verifyNoMoreInteractions(actionDelegateMock); 244 | } 245 | 246 | @Test 247 | public void testErrorHandlerBlockExecutorExceptionHandling() { 248 | InOrder testVerifier = inOrder(actionDelegateMock); 249 | 250 | ErrorHandler 251 | .createIsolated() 252 | .bindClass(DBError.class, errorCode -> throwable -> throwable instanceof DBErrorException && DBError.from((DBErrorException) throwable) == errorCode) 253 | .on(DBError.READ_ONLY, (throwable, errorHandler) -> { 254 | actionDelegateMock.action1(); 255 | errorHandler.skipAlways(); 256 | }) 257 | .run(() -> { 258 | throw new DBErrorException("read-only"); 259 | }); 260 | 261 | testVerifier.verify(actionDelegateMock).action1(); 262 | testVerifier.verifyNoMoreInteractions(); 263 | Mockito.verifyNoMoreInteractions(actionDelegateMock); 264 | } 265 | 266 | @Test 267 | public void testErrorHandlerBlockExecutorIgnoresNotMatchedException() { 268 | InOrder testVerifier = inOrder(actionDelegateMock); 269 | 270 | ErrorHandler 271 | .createIsolated() 272 | .bindClass(DBError.class, errorCode -> throwable -> throwable instanceof DBErrorException && DBError.from((DBErrorException) throwable) == errorCode) 273 | .on(DBError.READ_ONLY, (throwable, errorHandler) -> { 274 | actionDelegateMock.action1(); 275 | errorHandler.skipAlways(); 276 | }) 277 | .run(() -> { 278 | throw new DBErrorException("read"); 279 | }); 280 | 281 | testVerifier.verifyNoMoreInteractions(); 282 | Mockito.verifyNoMoreInteractions(actionDelegateMock); 283 | } 284 | 285 | @Test 286 | public void testErrorHandlerIfSkipDefaults() { 287 | InOrder testVerifier = inOrder(actionDelegateMock); 288 | 289 | ErrorHandler 290 | .create() 291 | .skipDefaults() 292 | .on("closed:bar", (throwable, handler) -> { 293 | actionDelegateMock.action1(); 294 | }) 295 | .run(() -> { 296 | throw new BarException("", false); 297 | }); 298 | 299 | testVerifier.verify(actionDelegateMock).action1(); 300 | Mockito.verifyNoMoreInteractions(actionDelegateMock); 301 | 302 | ErrorHandler 303 | .create() 304 | .on("closed:bar", (throwable, handler) -> { 305 | actionDelegateMock.action2(); 306 | }) 307 | .run(() -> { 308 | throw new BarException("", false); 309 | }); 310 | 311 | testVerifier.verify(actionDelegateMock).action2(); 312 | testVerifier.verify(actionDelegateMock).defaultAction3(); 313 | } 314 | 315 | private enum DBError { 316 | READ_ONLY, 317 | DEADLOCK, 318 | FATAL; 319 | 320 | public static DBError from(DBErrorException error) { 321 | switch (error.getMessage()) { 322 | case "read-only": 323 | return DBError.READ_ONLY; 324 | case "deadlock": 325 | return DBError.DEADLOCK; 326 | default: 327 | return DBError.FATAL; 328 | } 329 | } 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /errorhandler/src/test/java/com/workable/errorhandler/FooException.java: -------------------------------------------------------------------------------- 1 | package com.workable.errorhandler; 2 | 3 | /** 4 | * A checked test exception for using in {@link ErrorHandler} unit tests 5 | * 6 | * @author Stratos Pavlakis 7 | */ 8 | public class FooException extends Exception { 9 | 10 | private boolean fatal = false; 11 | 12 | public FooException(String message) { 13 | super(message); 14 | } 15 | 16 | public FooException(String message, boolean fatal) { 17 | super(message); 18 | this.fatal = fatal; 19 | } 20 | 21 | public boolean isFatal() { 22 | return fatal; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /errorhandler/src/test/java/com/workable/errorhandler/QuxException.java: -------------------------------------------------------------------------------- 1 | package com.workable.errorhandler; 2 | 3 | /** 4 | * A checked test exception for using in {@link ErrorHandler} unit tests 5 | * 6 | * @author Stratos Pavlakis 7 | */ 8 | public class QuxException extends Exception { 9 | 10 | private int errorStatus = 0; 11 | 12 | public QuxException(int errorStatus) { 13 | this.errorStatus = errorStatus; 14 | } 15 | 16 | public int getErrorStatus() { 17 | return errorStatus; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Workable/java-error-handler/1c16c8f487593eddd88b55f31e9c9bb8243522df/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Sep 09 12:25:52 EEST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'parent' 2 | include ':errorhandler' 3 | include ':errorhandler-matchers:retrofit-rx-matcher' 4 | --------------------------------------------------------------------------------