├── sample-jar ├── .gitignore ├── src │ └── main │ │ └── java │ │ └── com │ │ └── santarest │ │ └── sample │ │ ├── OuterAction.java │ │ ├── ErrorMessage.java │ │ ├── ExampleExecutableJar.java │ │ ├── BaseExampleAction.java │ │ ├── UploadFileAction.java │ │ ├── ExampleAction.java │ │ └── SamplesRunner.java ├── proguard-rules.pro └── build.gradle ├── sample-android ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ ├── styles.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── strings.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ ├── menu │ │ │ │ └── menu_main.xml │ │ │ └── layout │ │ │ │ └── activity_main.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── santarest │ │ │ │ └── sample │ │ │ │ ├── OuterAction.java │ │ │ │ ├── ErrorMessage.java │ │ │ │ ├── BaseExampleAction.java │ │ │ │ ├── UploadFileAction.java │ │ │ │ ├── ExampleAction.java │ │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml │ └── androidTest │ │ └── java │ │ └── com │ │ └── saltar │ │ └── sample │ │ └── ApplicationTest.java ├── proguard-rules.pro └── build.gradle ├── santarest-compiler ├── .gitignore ├── src │ └── main │ │ └── java │ │ └── com │ │ └── santarest │ │ ├── validation │ │ ├── Validator.java │ │ ├── ValidationError.java │ │ ├── AnnotationQuantityValidator.java │ │ ├── FieldsModifiersValidator.java │ │ ├── BodyValidator.java │ │ ├── RequestTypeValidator.java │ │ ├── ClassValidator.java │ │ ├── AnnotationTypesValidator.java │ │ ├── RestActionValidators.java │ │ └── PathValidator.java │ │ ├── Generator.java │ │ ├── FactoryGenerator.java │ │ ├── TypeUtils.java │ │ ├── SantaProcessor.java │ │ ├── RestActionClass.java │ │ └── HelpersGenerator.java └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ └── gradle-wrapper.jar ├── santarest ├── src │ └── main │ │ └── java │ │ └── com │ │ └── santarest │ │ ├── utils │ │ ├── Logger.java │ │ └── MimeUtil.java │ │ ├── SantaRestException.java │ │ ├── ActionPoster.java │ │ ├── http │ │ ├── ByteArrayBody.java │ │ ├── StringBody.java │ │ ├── Header.java │ │ ├── FormUrlEncodedRequestBody.java │ │ ├── Request.java │ │ ├── Response.java │ │ ├── HttpBody.java │ │ ├── FileBody.java │ │ └── MultipartRequestBody.java │ │ ├── callback │ │ ├── Callback.java │ │ ├── OttoPoster.java │ │ └── EventBusPoster.java │ │ ├── annotations │ │ ├── RequestHeader.java │ │ ├── ResponseHeader.java │ │ ├── Error.java │ │ ├── Status.java │ │ ├── Part.java │ │ ├── Path.java │ │ ├── Body.java │ │ ├── Response.java │ │ ├── Field.java │ │ ├── Query.java │ │ └── RestAction.java │ │ ├── client │ │ ├── AndroidApacheClient.java │ │ ├── HttpClient.java │ │ ├── OkClient.java │ │ ├── UrlConnectionClient.java │ │ └── ApacheClient.java │ │ ├── converter │ │ ├── Converter.java │ │ └── GsonConverter.java │ │ ├── RXOnSubscribe.java │ │ ├── Utils.java │ │ ├── Defaults.java │ │ ├── RequestBuilder.java │ │ └── SantaRest.java └── build.gradle ├── .gitignore ├── gradlew.bat ├── README.md └── gradlew /sample-jar/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample-android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /santarest-compiler/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':sample-android', ':santarest', ':santarest-compiler', ':sample-jar' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santagroup/santarest/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /sample-android/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santagroup/santarest/HEAD/sample-android/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample-android/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santagroup/santarest/HEAD/sample-android/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample-android/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santagroup/santarest/HEAD/sample-android/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample-android/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santagroup/santarest/HEAD/sample-android/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/validation/Validator.java: -------------------------------------------------------------------------------- 1 | package com.santarest.validation; 2 | 3 | import java.util.Set; 4 | 5 | public interface Validator { 6 | Set validate(T value); 7 | } 8 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/utils/Logger.java: -------------------------------------------------------------------------------- 1 | package com.santarest.utils; 2 | 3 | public interface Logger { 4 | 5 | void log(String message, String... args); 6 | 7 | void error(String message, String... args); 8 | } 9 | -------------------------------------------------------------------------------- /sample-android/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample-android/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /sample-jar/src/main/java/com/santarest/sample/OuterAction.java: -------------------------------------------------------------------------------- 1 | package com.santarest.sample; 2 | 3 | import com.santarest.annotations.RestAction; 4 | 5 | public class OuterAction { 6 | 7 | @RestAction("/events/") 8 | public static class InnerAction { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample-android/src/main/java/com/santarest/sample/OuterAction.java: -------------------------------------------------------------------------------- 1 | package com.santarest.sample; 2 | 3 | import com.santarest.annotations.RestAction; 4 | 5 | public class OuterAction { 6 | 7 | @RestAction("/events/") 8 | public static class InnerAction { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample-android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Saltar 3 | MainActivity 4 | 5 | Hello world! 6 | Settings 7 | 8 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/SantaRestException.java: -------------------------------------------------------------------------------- 1 | package com.santarest; 2 | 3 | /** 4 | * Created by dirong on 11/6/15. 5 | */ 6 | public class SantaRestException extends RuntimeException { 7 | 8 | public SantaRestException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample-jar/src/main/java/com/santarest/sample/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package com.santarest.sample; 2 | 3 | import com.google.gson.annotations.Expose; 4 | 5 | //Model object 6 | public class ErrorMessage { 7 | @Expose 8 | String errorMessage; 9 | 10 | public String getErrorMessage() { 11 | return errorMessage; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sample-android/src/main/java/com/santarest/sample/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package com.santarest.sample; 2 | 3 | import com.google.gson.annotations.Expose; 4 | 5 | //Model object 6 | public class ErrorMessage { 7 | @Expose 8 | String errorMessage; 9 | 10 | public String getErrorMessage() { 11 | return errorMessage; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/ActionPoster.java: -------------------------------------------------------------------------------- 1 | package com.santarest; 2 | 3 | /** 4 | * Created by dirong on 6/23/15. 5 | */ 6 | public abstract class ActionPoster { 7 | 8 | protected abstract void post(Object action); 9 | 10 | public abstract void subscribe(Object subscriber); 11 | 12 | public abstract void unsubscribe(Object subscriber); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/http/ByteArrayBody.java: -------------------------------------------------------------------------------- 1 | package com.santarest.http; 2 | 3 | public class ByteArrayBody extends HttpBody { 4 | 5 | private final byte[] bytes; 6 | 7 | public ByteArrayBody(String mimeType, byte[] bytes) { 8 | super(mimeType); 9 | this.bytes = bytes; 10 | } 11 | 12 | public byte[] getContent() { 13 | return bytes; 14 | } 15 | } -------------------------------------------------------------------------------- /sample-android/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /sample-jar/src/main/java/com/santarest/sample/ExampleExecutableJar.java: -------------------------------------------------------------------------------- 1 | package com.santarest.sample; 2 | 3 | /** 4 | * Created by vladla on 11/13/15. 5 | */ 6 | public class ExampleExecutableJar { 7 | 8 | public static void main(String... str) { 9 | SamplesRunner samplesRunner = new SamplesRunner(); 10 | samplesRunner.registerEvents(); 11 | samplesRunner.runTests(); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /sample-android/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /sample-android/src/androidTest/java/com/saltar/sample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.saltar.sample; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/callback/Callback.java: -------------------------------------------------------------------------------- 1 | package com.santarest.callback; 2 | 3 | public interface Callback { 4 | 5 | /** 6 | * Successful HTTP response. 7 | */ 8 | void onSuccess(A action); 9 | 10 | /** 11 | * Unsuccessful HTTP response due to network failure, non-2XX status code, or unexpected 12 | * exception. 13 | * 14 | * @param error 15 | */ 16 | void onFail(A action, Exception error); 17 | } 18 | -------------------------------------------------------------------------------- /santarest-compiler/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | targetCompatibility = '1.6' 4 | sourceCompatibility = '1.6' 5 | 6 | dependencies { 7 | compile project(':santarest') 8 | compile 'com.squareup:javapoet:1.1.0' 9 | compile 'com.google.auto.service:auto-service:1.0-rc2' 10 | compile 'com.google.code.findbugs:jsr305:2.0.3' 11 | compile 'org.apache.velocity:velocity:1.7' 12 | compile 'org.ow2.asm:asm:4.1' 13 | compile 'com.google.guava:guava:+' 14 | } 15 | -------------------------------------------------------------------------------- /sample-jar/src/main/java/com/santarest/sample/BaseExampleAction.java: -------------------------------------------------------------------------------- 1 | package com.santarest.sample; 2 | 3 | import com.santarest.annotations.Status; 4 | 5 | /** 6 | * This action class was created to show, 7 | * that action helper will be generated to fill the 8 | * annotated variables of super class too. 9 | */ 10 | public class BaseExampleAction { 11 | 12 | @Status 13 | boolean success; 14 | 15 | public boolean isSuccess() { 16 | return success; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample-android/src/main/java/com/santarest/sample/BaseExampleAction.java: -------------------------------------------------------------------------------- 1 | package com.santarest.sample; 2 | 3 | import com.santarest.annotations.Status; 4 | 5 | /** 6 | * This action class was created to show, 7 | * that action helper will be generated to fill the 8 | * annotated variables of super class too. 9 | */ 10 | public class BaseExampleAction { 11 | 12 | @Status 13 | boolean success; 14 | 15 | public boolean isSuccess() { 16 | return success; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /santarest/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'provided-base' 3 | 4 | sourceCompatibility = 1.6 5 | targetCompatibility = 1.6 6 | 7 | dependencies { 8 | compile 'com.google.code.gson:gson:+' 9 | provided 'de.greenrobot:eventbus:2.4.0' 10 | provided 'com.squareup:otto:+' 11 | provided 'com.google.android:android:+' 12 | provided 'com.squareup.okhttp:okhttp:+' 13 | provided 'com.squareup.okhttp:okhttp-urlconnection:+' 14 | provided 'io.reactivex:rxjava:+' 15 | } -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/annotations/RequestHeader.java: -------------------------------------------------------------------------------- 1 | package com.santarest.annotations; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.FIELD; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | @Documented 11 | @Retention(RUNTIME) 12 | @Target(FIELD) 13 | public @interface RequestHeader { 14 | String value(); 15 | } 16 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/annotations/ResponseHeader.java: -------------------------------------------------------------------------------- 1 | package com.santarest.annotations; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.FIELD; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | @Documented 11 | @Retention(RUNTIME) 12 | @Target(FIELD) 13 | public @interface ResponseHeader { 14 | String value(); 15 | } 16 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/annotations/Error.java: -------------------------------------------------------------------------------- 1 | package com.santarest.annotations; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.FIELD; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Created by dirong on 6/28/15. 12 | */ 13 | @Documented 14 | @Target(FIELD) 15 | @Retention(RUNTIME) 16 | public @interface Error { 17 | } 18 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/annotations/Status.java: -------------------------------------------------------------------------------- 1 | package com.santarest.annotations; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.FIELD; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Response status code. 12 | * Possible types of field: 13 | * Boolean.class, Integer.class, Long.class, String.class, boolean.class, int.class, long.class 14 | */ 15 | @Documented 16 | @Retention(RUNTIME) 17 | @Target(FIELD) 18 | public @interface Status { 19 | } 20 | -------------------------------------------------------------------------------- /sample-android/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/callback/OttoPoster.java: -------------------------------------------------------------------------------- 1 | package com.santarest.callback; 2 | 3 | import com.santarest.ActionPoster; 4 | import com.squareup.otto.Bus; 5 | 6 | /** 7 | * Created by dirong on 6/24/15. 8 | */ 9 | public class OttoPoster extends ActionPoster { 10 | 11 | private final Bus bus = new Bus(); 12 | 13 | @Override 14 | protected void post(Object action) { 15 | bus.post(action); 16 | } 17 | 18 | @Override 19 | public void subscribe(Object subscriber) { 20 | bus.register(subscriber); 21 | } 22 | 23 | @Override 24 | public void unsubscribe(Object subscriber) { 25 | bus.unregister(subscriber); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/client/AndroidApacheClient.java: -------------------------------------------------------------------------------- 1 | package com.santarest.client; 2 | 3 | import android.net.http.AndroidHttpClient; 4 | 5 | /** 6 | * Provides a {@link HttpClient} which uses the Android-specific version of 7 | * {@link org.apache.http.client.HttpClient}, {@link AndroidHttpClient}. 8 | *

9 | * If you need to provide a customized version of the {@link AndroidHttpClient} or a different 10 | * {@link org.apache.http.client.HttpClient} on Android use {@link ApacheClient} directly. 11 | */ 12 | public final class AndroidApacheClient extends ApacheClient { 13 | public AndroidApacheClient() { 14 | super(AndroidHttpClient.newInstance("Saltar")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample-jar/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/vladla/Downloads/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample-android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/dirong/Work/adt-bundle-mac-x86_64/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/annotations/Part.java: -------------------------------------------------------------------------------- 1 | package com.santarest.annotations; 2 | 3 | import com.santarest.http.MultipartRequestBody; 4 | 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.FIELD; 10 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 11 | 12 | /** 13 | * A single part of a multi-part request. 14 | */ 15 | @Documented 16 | @Target(FIELD) 17 | @Retention(RUNTIME) 18 | public @interface Part { 19 | String value(); 20 | 21 | /** 22 | * The {@code Content-Transfer-Encoding} of this part. 23 | */ 24 | String encoding() default MultipartRequestBody.DEFAULT_TRANSFER_ENCODING; 25 | } 26 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/annotations/Path.java: -------------------------------------------------------------------------------- 1 | package com.santarest.annotations; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.FIELD; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Annotation for request filling 12 | * Simple Example: 13 | *

14 |  * @RestAction(value = "/repos/{repo}/contributors")
15 |  * public class ExampleAction {
16 |  *    @Path("repo") Object repoName;
17 |  * }
18 |  * 
19 | */ 20 | @Documented 21 | @Retention(RUNTIME) 22 | @Target(FIELD) 23 | public @interface Path { 24 | String value(); 25 | 26 | boolean encode() default true; 27 | } 28 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/annotations/Body.java: -------------------------------------------------------------------------------- 1 | package com.santarest.annotations; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.FIELD; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Annotation for request body filling 12 | * Simple Example: 13 | *
14 |  * @Body
15 |  * Map body = new HashMap<>();
16 |  * @Body
17 |  * SomeObject someObjectName;
18 |  * 
19 | * This map is serialized to json 20 | */ 21 | @Documented 22 | @Target(FIELD) 23 | @Retention(RUNTIME) 24 | public @interface Body { 25 | 26 | // int[] value() default {}; //TODO: add ports config 27 | 28 | } 29 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/annotations/Response.java: -------------------------------------------------------------------------------- 1 | package com.santarest.annotations; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.FIELD; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Any success or failed response data. 12 | * Acceptable types 13 | * @see com.santarest.http.HttpBody 14 | * @see String 15 | * and any other type, which can be parsed with converter 16 | */ 17 | @Documented 18 | @Retention(RUNTIME) 19 | @Target(FIELD) 20 | public @interface Response { 21 | 22 | /** 23 | * HTTP status code of sever response. 24 | */ 25 | int value() default 0;/*for all statuses*/ 26 | } 27 | -------------------------------------------------------------------------------- /sample-jar/src/main/java/com/santarest/sample/UploadFileAction.java: -------------------------------------------------------------------------------- 1 | package com.santarest.sample; 2 | 3 | import com.santarest.annotations.Part; 4 | import com.santarest.annotations.Query; 5 | import com.santarest.annotations.Response; 6 | import com.santarest.annotations.RestAction; 7 | import com.santarest.annotations.Status; 8 | 9 | @RestAction(value = "/post.php", type = RestAction.Type.MULTIPART) 10 | public class UploadFileAction { 11 | 12 | @Query("dir") 13 | String name = "testDir"; 14 | @Part("name") 15 | String part = "sdfsadfdsafasfdasdfdasfasdfasdfasfsdfdsfasd"; 16 | @Response 17 | String response; 18 | @Status 19 | boolean success; 20 | 21 | public UploadFileAction() { 22 | } 23 | 24 | public String getResponse() { 25 | return response; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sample-android/src/main/java/com/santarest/sample/UploadFileAction.java: -------------------------------------------------------------------------------- 1 | package com.santarest.sample; 2 | 3 | import com.santarest.annotations.Part; 4 | import com.santarest.annotations.Query; 5 | import com.santarest.annotations.Response; 6 | import com.santarest.annotations.RestAction; 7 | import com.santarest.annotations.Status; 8 | 9 | @RestAction(value = "/post.php", type = RestAction.Type.MULTIPART) 10 | public class UploadFileAction { 11 | 12 | @Query("dir") 13 | String name = "testDir"; 14 | @Part("name") 15 | String part = "sdfsadfdsafasfdasdfdasfasdfasdfasfsdfdsfasd"; 16 | @Response 17 | String response; 18 | @Status 19 | boolean success; 20 | 21 | public UploadFileAction() { 22 | } 23 | 24 | public String getResponse() { 25 | return response; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/validation/ValidationError.java: -------------------------------------------------------------------------------- 1 | package com.santarest.validation; 2 | 3 | import javax.lang.model.element.Element; 4 | 5 | /** 6 | * Created by dirong on 6/28/15. 7 | */ 8 | public class ValidationError { 9 | 10 | private final String message; 11 | 12 | private final Element element; 13 | 14 | public ValidationError(String message, Element element, String... args) { 15 | this(String.format(message, (Object[])args), element); 16 | } 17 | 18 | public ValidationError(String message, Element element) { 19 | this.message = message; 20 | this.element = element; 21 | } 22 | 23 | public String getMessage() { 24 | return message; 25 | } 26 | 27 | public Element getElement() { 28 | return element; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/annotations/Field.java: -------------------------------------------------------------------------------- 1 | package com.santarest.annotations; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.FIELD; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Named pair for a form-encoded request. 12 | *

13 | * Values are converted to strings using {@link Object#toString()} and then form URL encoded. 14 | * {@code null} values are ignored. Passing a {@link java.util.List List} or array will result in a 15 | * field pair for each non-{@code null} item. 16 | * 17 | * @see FieldMap 18 | */ 19 | @Documented 20 | @Target(FIELD) 21 | @Retention(RUNTIME) 22 | public @interface Field { 23 | String value(); 24 | } 25 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/converter/Converter.java: -------------------------------------------------------------------------------- 1 | package com.santarest.converter; 2 | 3 | import com.santarest.http.HttpBody; 4 | 5 | import java.lang.reflect.Type; 6 | 7 | public interface Converter { 8 | 9 | /** 10 | * Convert an HTTP response body to a concrete object of the specified type. 11 | * 12 | * @param body HTTP response body. 13 | * @param type Target object type. 14 | * @return Instance of {@code type} which will be cast by the caller. 15 | */ 16 | Object fromBody(HttpBody body, Type type); 17 | 18 | /** 19 | * Convert an object to an appropriate representation for HTTP transport. 20 | * 21 | * @param object Object instance to convert. 22 | * @return Representation of the specified object as bytes. 23 | */ 24 | HttpBody toBody(Object object); 25 | } 26 | -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/Generator.java: -------------------------------------------------------------------------------- 1 | package com.santarest; 2 | 3 | import com.squareup.javapoet.JavaFile; 4 | import com.squareup.javapoet.TypeSpec; 5 | 6 | import java.io.IOException; 7 | import java.util.ArrayList; 8 | 9 | import javax.annotation.processing.Filer; 10 | 11 | public abstract class Generator { 12 | 13 | private final Filer filer; 14 | 15 | protected Generator(Filer filer) { 16 | this.filer = filer; 17 | } 18 | 19 | abstract void generate(ArrayList actionClasses); 20 | 21 | protected void saveClass(String packageName, TypeSpec typeSpec) { 22 | try { 23 | JavaFile.builder(packageName, typeSpec).build().writeTo(filer); 24 | } catch (IOException e) { 25 | e.printStackTrace(); 26 | throw new RuntimeException(e.getMessage()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/http/StringBody.java: -------------------------------------------------------------------------------- 1 | package com.santarest.http; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | 5 | public class StringBody extends ByteArrayBody { 6 | 7 | public StringBody(String string) { 8 | super("text/plain; charset=UTF-8", convertToBytes(string)); 9 | } 10 | 11 | private static byte[] convertToBytes(String string) { 12 | try { 13 | return string.getBytes("UTF-8"); 14 | } catch (UnsupportedEncodingException e) { 15 | throw new RuntimeException(e); 16 | } 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | try { 22 | return "StringBody[" + new String(getContent(), "UTF-8") + "]"; 23 | } catch (UnsupportedEncodingException e) { 24 | throw new AssertionError("Must be able to decode UTF-8"); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/RXOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package com.santarest; 2 | 3 | import rx.Observable; 4 | import rx.Subscriber; 5 | import rx.exceptions.Exceptions; 6 | 7 | abstract class RXOnSubscribe implements Observable.OnSubscribe { 8 | 9 | private final A action; 10 | 11 | RXOnSubscribe(A action) { 12 | this.action = action; 13 | } 14 | 15 | @Override 16 | public void call(Subscriber subscriber) { 17 | try { 18 | doAction(action); 19 | if (!subscriber.isUnsubscribed()) { 20 | subscriber.onNext(action); 21 | } 22 | } catch (final Exception e) { 23 | Exceptions.throwIfFatal(e); 24 | if (!subscriber.isUnsubscribed()) { 25 | subscriber.onError(e); 26 | } 27 | } 28 | } 29 | 30 | protected abstract void doAction(A action); 31 | } -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/annotations/Query.java: -------------------------------------------------------------------------------- 1 | package com.santarest.annotations; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.FIELD; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Query parameter appended to the URL. 12 | */ 13 | @Documented 14 | @Target(FIELD) 15 | @Retention(RUNTIME) 16 | public @interface Query { 17 | /** 18 | * The query parameter name. 19 | */ 20 | String value(); 21 | 22 | /** 23 | * Specifies whether {@link #value()} is URL encoded. 24 | */ 25 | boolean encodeName() default false; 26 | 27 | /** 28 | * Specifies whether the argument value to the annotated method parameter is URL encoded. 29 | */ 30 | boolean encodeValue() default true; 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.class 3 | R.java 4 | *.apk 5 | *.ap_ 6 | bin/ 7 | gen/ 8 | classes.dex 9 | datingchat.properties 10 | chat/BeNaughty/dating-resources/backup-res/* 11 | .svn/ 12 | .settings/ 13 | local.properties 14 | chat/BeNaughty/tmp/ 15 | additional.properties 16 | Dating/build_util/ 17 | out/ 18 | lint.xml 19 | vcs.xml 20 | classes/ 21 | Dating/DatingSDKFragments/.idea/workspace.xml 22 | ant.xml 23 | */project.properties 24 | Manifest.java 25 | codeStyleSettings.xml 26 | Dating/ILikeBoardProject/Canoodle/.idea/workspace.xml 27 | proguard_logs 28 | .classpath 29 | ant.xml 30 | build 31 | *.properties 32 | out/ 33 | **/.idea/workspace.xml 34 | .idea/workspace.xml 35 | /*/*/.idea/workspace.xml 36 | .idea/inspectionProfiles 37 | **/.idea/inspectionProfiles 38 | */*/*/.idea/inspectionProfiles 39 | CupidLabs/Yolo/res/values/config_private.xml 40 | .idea 41 | *.iml 42 | .gradle 43 | com_crashlytics_export_strings.xml 44 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/callback/EventBusPoster.java: -------------------------------------------------------------------------------- 1 | package com.santarest.callback; 2 | 3 | import com.santarest.ActionPoster; 4 | 5 | import de.greenrobot.event.EventBus; 6 | 7 | /** 8 | * Created by dirong on 6/23/15. 9 | */ 10 | public class EventBusPoster extends ActionPoster { 11 | 12 | private final EventBus eventBus; 13 | 14 | public EventBusPoster(EventBus eventBus) { 15 | this.eventBus = eventBus; 16 | } 17 | 18 | public EventBusPoster() { 19 | this(new EventBus()); 20 | } 21 | 22 | @Override 23 | protected void post(Object action) { 24 | eventBus.post(action); 25 | } 26 | 27 | @Override 28 | public void subscribe(Object subscriber) { 29 | eventBus.register(subscriber); 30 | } 31 | 32 | @Override 33 | public void unsubscribe(Object subscriber) { 34 | eventBus.unregister(subscriber); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sample-jar/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | 4 | dependencies { 5 | compile 'com.squareup:otto:+' 6 | compile project(":santarest") 7 | compile project(':santarest-compiler') 8 | compile'io.reactivex:rxjava:1.0.15' 9 | 10 | } 11 | 12 | 13 | version = '1.0' 14 | task jarWithDependencies(type: Jar) { 15 | manifest { 16 | attributes 'Implementation-Title': 'SantaRest jar example', 17 | 'Implementation-Version': version, 18 | 'Main-Class': 'com.santarest.sample.ExampleExecutableJar' 19 | } 20 | baseName = project.name + '-all' 21 | from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } 22 | with jar 23 | } 24 | 25 | jar { 26 | manifest { 27 | attributes 'Main-Class': 'com.santarest.sample.ExampleExecutableJar', 28 | 'Class-Path': configurations.runtime.files.collect {"$it.name"}.join(' ') 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sample-android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /sample-android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.neenbedankt.android-apt' 3 | 4 | sourceCompatibility = 1.6 5 | targetCompatibility = 1.6 6 | 7 | android { 8 | compileSdkVersion 22 9 | buildToolsVersion "22.0.1" 10 | 11 | defaultConfig { 12 | applicationId "com.santarest.sample" 13 | minSdkVersion 10 14 | targetSdkVersion 21 15 | versionCode 1 16 | versionName "1.0" 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | compile 'com.android.support:appcompat-v7:22.2.0' 28 | compile 'com.squareup:otto:+' 29 | // compile 'com.github.santagroup:santarest:0.0.1' 30 | // apt 'com.github.santagroup:santarest-compiler:0.0.1' 31 | compile project(":santarest") 32 | apt project(':santarest-compiler') 33 | compile'io.reactivex:rxjava:1.0.15' 34 | } 35 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/client/HttpClient.java: -------------------------------------------------------------------------------- 1 | package com.santarest.client; 2 | 3 | 4 | import com.santarest.http.Request; 5 | import com.santarest.http.Response; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * Abstraction of an HTTP client which can execute {@link Request Requests}. This class must be 11 | * thread-safe as invocation may happen from multiple threads simultaneously. 12 | */ 13 | public interface HttpClient { 14 | 15 | int CONNECT_TIMEOUT_MILLIS = 15 * 1000; // 15s 16 | int READ_TIMEOUT_MILLIS = 20 * 1000; // 20s 17 | 18 | /** 19 | * Synchronously execute an HTTP represented by {@code request} and encapsulate all response data 20 | * into a {@link Response} instance. 21 | *

22 | * Note: If the request has a body, its length and mime type will have already been added to the 23 | * header list as {@code Content-Length} and {@code Content-Type}, respectively. Do NOT alter 24 | * these values as they might have been set as a result of an application-level configuration. 25 | */ 26 | Response execute(Request request) throws IOException; 27 | } 28 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/utils/MimeUtil.java: -------------------------------------------------------------------------------- 1 | package com.santarest.utils; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | import static java.util.regex.Pattern.CASE_INSENSITIVE; 7 | 8 | public final class MimeUtil { 9 | private static final Pattern CHARSET = Pattern.compile("\\Wcharset=([^\\s;]+)", CASE_INSENSITIVE); 10 | 11 | /** 12 | * Parse the MIME type from a {@code Content-Type} header value or default to "UTF-8". 13 | * 14 | * @deprecated Use {@link #parseCharset(String, String)}. 15 | */ 16 | @Deprecated 17 | public static String parseCharset(String mimeType) { 18 | return parseCharset(mimeType, "UTF-8"); 19 | } 20 | 21 | /** 22 | * Parse the MIME type from a {@code Content-Type} header value. 23 | */ 24 | public static String parseCharset(String mimeType, String defaultCharset) { 25 | Matcher match = CHARSET.matcher(mimeType); 26 | if (match.find()) { 27 | return match.group(1).replaceAll("[\"\\\\]", ""); 28 | } 29 | return defaultCharset; 30 | } 31 | 32 | private MimeUtil() { 33 | // No instances. 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/validation/AnnotationQuantityValidator.java: -------------------------------------------------------------------------------- 1 | package com.santarest.validation; 2 | 3 | import com.santarest.RestActionClass; 4 | 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | import javax.lang.model.element.Element; 10 | 11 | /** 12 | * Created by dirong on 6/28/15. 13 | */ 14 | public class AnnotationQuantityValidator implements Validator { 15 | 16 | private final Class annotationClass; 17 | private final int maxQuantity; 18 | 19 | public AnnotationQuantityValidator(Class annotationClass, int maxQuantity) { 20 | this.annotationClass = annotationClass; 21 | this.maxQuantity = maxQuantity; 22 | } 23 | 24 | @Override 25 | public Set validate(RestActionClass value) { 26 | Set errors = new HashSet(); 27 | List annotations = value.getAnnotatedElements(annotationClass); 28 | if (annotations.size() > maxQuantity) { 29 | errors.add(new ValidationError("There are more then one field annotated with %s", annotations.get(maxQuantity), annotationClass.getName())); 30 | } 31 | return errors; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/validation/FieldsModifiersValidator.java: -------------------------------------------------------------------------------- 1 | package com.santarest.validation; 2 | 3 | import com.santarest.RestActionClass; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | import javax.lang.model.element.Element; 9 | import javax.lang.model.element.ElementKind; 10 | import javax.lang.model.element.Modifier; 11 | 12 | /** 13 | * Created by dirong on 6/28/15. 14 | */ 15 | public class FieldsModifiersValidator implements Validator { 16 | @Override 17 | public Set validate(RestActionClass value) { 18 | Set messages = new HashSet(); 19 | for (Element element : value.getAllAnnotatedMembers()) { 20 | if (element.getKind() != ElementKind.FIELD) continue; 21 | boolean hasPrivateModifier = element.getModifiers().contains(Modifier.PRIVATE); 22 | boolean hasStaticModifier = element.getModifiers().contains(Modifier.STATIC); 23 | if (hasStaticModifier || hasPrivateModifier) { 24 | messages.add(new ValidationError("Annotated fields must not be private or static.", element)); 25 | } 26 | } 27 | return messages; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/client/OkClient.java: -------------------------------------------------------------------------------- 1 | package com.santarest.client; 2 | 3 | import com.santarest.http.Request; 4 | import com.squareup.okhttp.OkHttpClient; 5 | import com.squareup.okhttp.OkUrlFactory; 6 | 7 | import java.io.IOException; 8 | import java.net.HttpURLConnection; 9 | import java.net.URL; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * SantaRest client that uses OkHttp. 14 | */ 15 | public class OkClient extends UrlConnectionClient { 16 | private static OkHttpClient generateDefaultOkHttp() { 17 | OkHttpClient client = new OkHttpClient(); 18 | client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 19 | client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 20 | return client; 21 | } 22 | 23 | private final OkUrlFactory okUrlFactory; 24 | 25 | public OkClient() { 26 | this(generateDefaultOkHttp()); 27 | } 28 | 29 | public OkClient(OkHttpClient client) { 30 | this.okUrlFactory = new OkUrlFactory(client); 31 | } 32 | 33 | @Override 34 | protected HttpURLConnection openConnection(Request request) throws IOException { 35 | return okUrlFactory.open(new URL(request.getUrl())); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/http/Header.java: -------------------------------------------------------------------------------- 1 | package com.santarest.http; 2 | 3 | public final class Header { 4 | private final String name; 5 | private final String value; 6 | 7 | public Header(String name, String value) { 8 | this.name = name; 9 | this.value = value; 10 | } 11 | 12 | public String getName() { 13 | return name; 14 | } 15 | 16 | public String getValue() { 17 | return value; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | if (this == o) return true; 23 | if (o == null || getClass() != o.getClass()) return false; 24 | 25 | Header header = (Header) o; 26 | 27 | if (name != null ? !name.equals(header.name) : header.name != null) return false; 28 | if (value != null ? !value.equals(header.value) : header.value != null) return false; 29 | 30 | return true; 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | int result = name != null ? name.hashCode() : 0; 36 | result = 31 * result + (value != null ? value.hashCode() : 0); 37 | return result; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return (name != null ? name : "") + ": " + (value != null ? value : ""); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/validation/BodyValidator.java: -------------------------------------------------------------------------------- 1 | package com.santarest.validation; 2 | 3 | import com.santarest.RestActionClass; 4 | import com.santarest.annotations.Body; 5 | import com.santarest.annotations.RestAction; 6 | 7 | import java.util.ArrayList; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | import javax.lang.model.element.Element; 13 | 14 | /** 15 | * Created by dirong on 6/28/15. 16 | */ 17 | public class BodyValidator implements Validator { 18 | @Override 19 | public Set validate(RestActionClass value) { 20 | Set errors = new HashSet(); 21 | List annotations = value.getAnnotatedElements(Body.class); 22 | if (annotations.isEmpty()) return errors; 23 | Element element = annotations.get(0); 24 | 25 | if (value.getMethod().hasBody()) return errors; 26 | 27 | List methodNames = new ArrayList(); 28 | for (RestAction.Method method : RestAction.Method.values()) { 29 | if (!method.hasBody()) continue; 30 | methodNames.add(method.name()); 31 | } 32 | errors.add(new ValidationError(String.format("It's possible to use %s only with %s methods ", element, Body.class.getName(), methodNames.toString()), value.getTypeElement())); 33 | return errors; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/annotations/RestAction.java: -------------------------------------------------------------------------------- 1 | package com.santarest.annotations; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.TYPE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Annotation for action class. Contains configurations for: 12 | * - Request method 13 | * - Request type 14 | * - The second part of request url 15 | */ 16 | @Documented 17 | @Target(TYPE) 18 | @Retention(RUNTIME) 19 | public @interface RestAction { 20 | 21 | Method method() default Method.GET; 22 | 23 | String value() default ""; 24 | 25 | Type type() default Type.SIMPLE; 26 | 27 | String[] headers() default {}; 28 | 29 | enum Type { 30 | /** 31 | * No content-specific logic required. 32 | */ 33 | SIMPLE, 34 | /** 35 | * Multi-part request body. 36 | */ 37 | MULTIPART, 38 | /** 39 | * Form URL-encoded request body. 40 | */ 41 | FORM_URL_ENCODED 42 | } 43 | 44 | enum Method { 45 | GET(false), POST(true), PUT(true), DELETE(false), HEAD(false), PATCH(true); 46 | 47 | private boolean hasBody; 48 | 49 | Method(boolean hasBody) { 50 | this.hasBody = hasBody; 51 | } 52 | 53 | public boolean hasBody() { 54 | return hasBody; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/http/FormUrlEncodedRequestBody.java: -------------------------------------------------------------------------------- 1 | package com.santarest.http; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.net.URLEncoder; 6 | 7 | public final class FormUrlEncodedRequestBody extends HttpBody { 8 | final ByteArrayOutputStream content = new ByteArrayOutputStream(); 9 | 10 | public FormUrlEncodedRequestBody() { 11 | super(null); 12 | } 13 | 14 | public void addField(String name, String value) { 15 | if (name == null) { 16 | throw new NullPointerException("name"); 17 | } 18 | if (value == null) { 19 | throw new NullPointerException("value"); 20 | } 21 | if (content.size() > 0) { 22 | content.write('&'); 23 | } 24 | try { 25 | name = URLEncoder.encode(name, "UTF-8"); 26 | value = URLEncoder.encode(value, "UTF-8"); 27 | 28 | content.write(name.getBytes("UTF-8")); 29 | content.write('='); 30 | content.write(value.getBytes("UTF-8")); 31 | } catch (IOException e) { 32 | throw new RuntimeException(e); 33 | } 34 | } 35 | 36 | @Override 37 | public String fileName() { 38 | return null; 39 | } 40 | 41 | @Override 42 | public String mimeType() { 43 | return "application/x-www-form-urlencoded; charset=UTF-8"; 44 | } 45 | 46 | @Override 47 | public byte[] getContent() { 48 | return content.toByteArray(); 49 | } 50 | } -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/validation/RequestTypeValidator.java: -------------------------------------------------------------------------------- 1 | package com.santarest.validation; 2 | 3 | import com.santarest.RestActionClass; 4 | import com.santarest.annotations.RestAction; 5 | 6 | import java.util.Arrays; 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | import javax.lang.model.element.Element; 12 | 13 | /** 14 | * Created by dirong on 6/28/15. 15 | */ 16 | public class RequestTypeValidator implements Validator { 17 | 18 | private final Class annotationClass; 19 | private final RestAction.Type[] requestTypes; 20 | 21 | public RequestTypeValidator(Class annotationClass, RestAction.Type... requestTypes) { 22 | this.annotationClass = annotationClass; 23 | this.requestTypes = requestTypes; 24 | } 25 | 26 | @Override 27 | public Set validate(RestActionClass value) { 28 | Set errors = new HashSet(); 29 | String bodyName = annotationClass.getSimpleName(); 30 | List typesList = Arrays.asList(requestTypes); 31 | for (Element element : value.getTypeElement().getEnclosedElements()) { 32 | if (element.getAnnotation(annotationClass) == null) continue; 33 | if (!typesList.contains(value.getRequestType())) { 34 | errors.add(new ValidationError("It's possible to use %s only with %s request types ", element, bodyName, typesList.toString())); 35 | } 36 | } 37 | return errors; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/validation/ClassValidator.java: -------------------------------------------------------------------------------- 1 | package com.santarest.validation; 2 | 3 | import com.santarest.annotations.RestAction; 4 | 5 | import java.util.Collections; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | import javax.lang.model.element.Element; 10 | import javax.lang.model.element.ElementKind; 11 | import javax.lang.model.element.Modifier; 12 | import javax.lang.model.element.TypeElement; 13 | 14 | public class ClassValidator implements Validator { 15 | 16 | private final static String SALTAR_ACTION_CLASS = RestAction.class.getSimpleName(); 17 | 18 | @Override 19 | public Set validate(Element value) { 20 | Set errors = new HashSet(); 21 | TypeElement typeElement = (TypeElement) value; 22 | if (typeElement.getKind() != ElementKind.CLASS) { 23 | errors.add(new ValidationError(String.format("Only classes can be annotated with @%s", SALTAR_ACTION_CLASS), value)); 24 | } 25 | String annotatedClassName = typeElement.getQualifiedName().toString(); 26 | if (!typeElement.getModifiers().contains(Modifier.PUBLIC)) { 27 | errors.add(new ValidationError(String.format("The class %s is not public.", annotatedClassName), value)); 28 | } 29 | // Check if it's an abstract class 30 | if (typeElement.getModifiers().contains(Modifier.ABSTRACT)) { 31 | errors.add(new ValidationError(String.format("The class %s is abstract. You can't annotate abstract classes with @%s", 32 | annotatedClassName, SALTAR_ACTION_CLASS), value)); 33 | } 34 | return Collections.emptySet(); 35 | } 36 | } -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/validation/AnnotationTypesValidator.java: -------------------------------------------------------------------------------- 1 | package com.santarest.validation; 2 | 3 | import com.santarest.RestActionClass; 4 | import com.santarest.TypeUtils; 5 | import com.squareup.javapoet.TypeName; 6 | 7 | import java.lang.annotation.Annotation; 8 | import java.lang.reflect.Type; 9 | import java.util.ArrayList; 10 | import java.util.HashSet; 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | import javax.lang.model.element.Element; 15 | 16 | /** 17 | * Created by dirong on 6/28/15. 18 | */ 19 | public class AnnotationTypesValidator implements Validator { 20 | 21 | private final Class annotationClass; 22 | private final Type[] types; 23 | private final ArrayList typeNames; 24 | 25 | public AnnotationTypesValidator(Class annotationClass, Type... types) { 26 | this.annotationClass = annotationClass; 27 | this.types = types; 28 | typeNames = new ArrayList(); 29 | for (Type type : types) { 30 | typeNames.add(TypeName.get(type).toString()); 31 | } 32 | } 33 | 34 | @Override 35 | public Set validate(RestActionClass value) { 36 | Set errors = new HashSet(); 37 | List elements = value.getAnnotatedElements(annotationClass); 38 | for (Element element : elements) { 39 | Annotation annotation = element.getAnnotation(annotationClass); 40 | if (TypeUtils.containsType(element, types)) { 41 | continue; 42 | } 43 | errors.add(new ValidationError("Fields annotated with %s should one from these types %s", element, annotation.toString(), typeNames.toString())); 44 | } 45 | return errors; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/FactoryGenerator.java: -------------------------------------------------------------------------------- 1 | package com.santarest; 2 | 3 | import com.squareup.javapoet.MethodSpec; 4 | import com.squareup.javapoet.ParameterizedTypeName; 5 | import com.squareup.javapoet.TypeSpec; 6 | 7 | import java.util.ArrayList; 8 | 9 | import javax.annotation.processing.Filer; 10 | import javax.lang.model.element.Modifier; 11 | 12 | public class FactoryGenerator extends Generator { 13 | 14 | public FactoryGenerator(Filer filer) { 15 | super(filer); 16 | } 17 | 18 | @Override 19 | public void generate(ArrayList actionClasses) { 20 | TypeSpec.Builder classBuilder = TypeSpec.classBuilder(SantaRest.HELPERS_FACTORY_CLASS_SIMPLE_NAME) 21 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 22 | .addJavadoc("Autogenerated class factory for instationa action helpers") 23 | .addSuperinterface(ParameterizedTypeName.get(SantaRest.ActionHelperFactory.class)); 24 | 25 | MethodSpec.Builder makeMethodBuilder = MethodSpec.methodBuilder("make") 26 | .addModifiers(Modifier.PUBLIC) 27 | .addAnnotation(Override.class) 28 | .returns(SantaRest.ActionHelper.class) 29 | .addParameter(Class.class, "actionClass"); 30 | 31 | for (RestActionClass actionClass : actionClasses) { 32 | makeMethodBuilder.beginControlFlow("if(actionClass == $L.class)", actionClass.getTypeElement().getQualifiedName()); 33 | makeMethodBuilder.addStatement(" return new $L()", actionClass.getFullHelperName()); 34 | makeMethodBuilder.endControlFlow(); 35 | } 36 | makeMethodBuilder.addStatement("return null"); 37 | classBuilder.addMethod(makeMethodBuilder.build()); 38 | saveClass(SantaRest.class.getPackage().getName(), classBuilder.build()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/http/Request.java: -------------------------------------------------------------------------------- 1 | package com.santarest.http; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | 8 | public final class Request { 9 | 10 | private final String method; 11 | private final String url; 12 | private final List

headers; 13 | private final HttpBody body; 14 | 15 | public Request(String method, String url, List
headers, HttpBody body) { 16 | if (method == null) { 17 | throw new NullPointerException("Method must not be null."); 18 | } 19 | if (url == null) { 20 | throw new NullPointerException("URL must not be null."); 21 | } 22 | this.method = method; 23 | this.url = url; 24 | 25 | if (headers == null) { 26 | this.headers = Collections.emptyList(); 27 | } else { 28 | this.headers = Collections.unmodifiableList(new ArrayList
(headers)); 29 | } 30 | 31 | this.body = body; 32 | } 33 | 34 | /** 35 | * HTTP method verb. 36 | */ 37 | public String getMethod() { 38 | return method; 39 | } 40 | 41 | /** 42 | * Target URL. 43 | */ 44 | public String getUrl() { 45 | return url; 46 | } 47 | 48 | /** 49 | * Returns an unmodifiable list of headers, never {@code null}. 50 | */ 51 | public List
getHeaders() { 52 | return headers; 53 | } 54 | 55 | /** 56 | * Returns the request body or {@code null}. 57 | */ 58 | public HttpBody getBody() { 59 | return body; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return "Request{" + 65 | "body=" + body + 66 | ", headers=" + headers + 67 | ", url='" + url + '\'' + 68 | ", method='" + method + '\'' + 69 | '}'; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /sample-jar/src/main/java/com/santarest/sample/ExampleAction.java: -------------------------------------------------------------------------------- 1 | package com.santarest.sample; 2 | 3 | import com.santarest.annotations.Error; 4 | import com.santarest.annotations.Path; 5 | import com.santarest.annotations.Query; 6 | import com.santarest.annotations.RequestHeader; 7 | import com.santarest.annotations.Response; 8 | import com.santarest.annotations.ResponseHeader; 9 | import com.santarest.annotations.RestAction; 10 | import com.santarest.annotations.Status; 11 | import com.santarest.http.HttpBody; 12 | 13 | import java.util.List; 14 | 15 | @RestAction(value = "/repos/{owner}/{repo}/contributors", 16 | type = RestAction.Type.SIMPLE, 17 | method = RestAction.Method.GET) 18 | public class ExampleAction extends BaseExampleAction { 19 | 20 | @Path("owner") 21 | Object ownerr; 22 | @Path("repo") 23 | Object repoo; 24 | @Query("repo") 25 | int query; 26 | @Query("repo2") 27 | boolean query2; 28 | @Query("repo3") 29 | double query3; 30 | 31 | @Response 32 | HttpBody responseBody; 33 | @Response 34 | List contributorss; 35 | @Response 36 | String string; 37 | @Response(404) 38 | String errorResponse404; 39 | @Response(401) 40 | ErrorMessage errorResponse401; 41 | 42 | @Status 43 | long status; 44 | @Status 45 | boolean success; 46 | 47 | @ResponseHeader("X-GitHub-Request-Id") 48 | String responseId; 49 | 50 | @RequestHeader("X-GitHub-Request-Id") 51 | String requestId; 52 | 53 | @Error 54 | Exception error; 55 | 56 | @Response 57 | String errorResponse; 58 | 59 | public ExampleAction(String owner, String repo) { 60 | this.ownerr = owner; 61 | this.repoo = repo; 62 | } 63 | 64 | public List getContributors() { 65 | return contributorss; 66 | } 67 | 68 | public HttpBody getResponseBody() { 69 | return responseBody; 70 | } 71 | 72 | 73 | public String getRequestId() { 74 | return requestId; 75 | } 76 | 77 | public static class Contributor { 78 | String login; 79 | int contributions; 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | return "ExampleAction{" + 85 | "owner='" + ownerr + '\'' + 86 | ", repo='" + repoo + '\'' + 87 | ", contributors=" + contributorss + 88 | ", responseBody=" + responseBody + 89 | ", status=" + status + 90 | '}'; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /sample-android/src/main/java/com/santarest/sample/ExampleAction.java: -------------------------------------------------------------------------------- 1 | package com.santarest.sample; 2 | 3 | import com.santarest.annotations.Error; 4 | import com.santarest.annotations.Path; 5 | import com.santarest.annotations.Query; 6 | import com.santarest.annotations.RequestHeader; 7 | import com.santarest.annotations.Response; 8 | import com.santarest.annotations.ResponseHeader; 9 | import com.santarest.annotations.RestAction; 10 | import com.santarest.annotations.Status; 11 | import com.santarest.http.HttpBody; 12 | 13 | import java.util.List; 14 | 15 | @RestAction(value = "/repos/{owner}/{repo}/contributors", 16 | type = RestAction.Type.SIMPLE, 17 | method = RestAction.Method.GET) 18 | public class ExampleAction extends BaseExampleAction { 19 | 20 | @Path("owner") 21 | Object ownerr; 22 | @Path("repo") 23 | Object repoo; 24 | @Query("repo") 25 | int query; 26 | @Query("repo2") 27 | boolean query2; 28 | @Query("repo3") 29 | double query3; 30 | 31 | @Response 32 | HttpBody responseBody; 33 | @Response 34 | List contributorss; 35 | @Response 36 | String string; 37 | @Response(404) 38 | String errorResponse404; 39 | @Response(401) 40 | ErrorMessage errorResponse401; 41 | 42 | @Status 43 | long status; 44 | @Status 45 | boolean success; 46 | 47 | @ResponseHeader("X-GitHub-Request-Id") 48 | String responseId; 49 | 50 | @RequestHeader("X-GitHub-Request-Id") 51 | String requestId; 52 | 53 | @Error 54 | Exception error; 55 | 56 | @Response 57 | String errorResponse; 58 | 59 | public ExampleAction(String owner, String repo) { 60 | this.ownerr = owner; 61 | this.repoo = repo; 62 | } 63 | 64 | public List getContributors() { 65 | return contributorss; 66 | } 67 | 68 | public HttpBody getResponseBody() { 69 | return responseBody; 70 | } 71 | 72 | 73 | public String getRequestId() { 74 | return requestId; 75 | } 76 | 77 | public static class Contributor { 78 | String login; 79 | int contributions; 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | return "ExampleAction{" + 85 | "owner='" + ownerr + '\'' + 86 | ", repo='" + repoo + '\'' + 87 | ", contributors=" + contributorss + 88 | ", responseBody=" + responseBody + 89 | ", status=" + status + 90 | '}'; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/http/Response.java: -------------------------------------------------------------------------------- 1 | package com.santarest.http; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | public final class Response { 8 | private final String url; 9 | private final int status; 10 | private final String reason; 11 | private final List
headers; 12 | private final HttpBody body; 13 | 14 | public Response(String url, int status, String reason, List
headers, HttpBody body) { 15 | if (url == null) { 16 | throw new IllegalArgumentException("url == null"); 17 | } 18 | if (status < 200) { 19 | throw new IllegalArgumentException("Invalid status code: " + status); 20 | } 21 | if (reason == null) { 22 | throw new IllegalArgumentException("reason == null"); 23 | } 24 | if (headers == null) { 25 | throw new IllegalArgumentException("headers == null"); 26 | } 27 | 28 | this.url = url; 29 | this.status = status; 30 | this.reason = reason; 31 | this.headers = Collections.unmodifiableList(new ArrayList
(headers)); 32 | this.body = body; 33 | } 34 | 35 | /** 36 | * Request URL. 37 | */ 38 | public String getUrl() { 39 | return url; 40 | } 41 | 42 | /** 43 | * Status code. 44 | */ 45 | public int getStatus() { 46 | return status; 47 | } 48 | 49 | /** 50 | * Returns true if the code is in [200..300), which means the request was 51 | * successfully received, understood, and accepted. 52 | */ 53 | public boolean isSuccessful() { 54 | return status >= 200 && status < 300; 55 | } 56 | 57 | /** 58 | * Status line reason phrase. 59 | */ 60 | public String getReason() { 61 | return reason; 62 | } 63 | 64 | /** 65 | * An unmodifiable collection of headers. 66 | */ 67 | public List
getHeaders() { 68 | return headers; 69 | } 70 | 71 | /** 72 | * Response body. May be {@code null}. 73 | */ 74 | public HttpBody getBody() { 75 | return body; 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return "Response{" + 81 | "url='" + url + '\'' + 82 | ", status=" + status + 83 | ", reason='" + reason + '\'' + 84 | ", headers=" + headers + 85 | ", body=" + body + 86 | '}'; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/converter/GsonConverter.java: -------------------------------------------------------------------------------- 1 | package com.santarest.converter; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonParseException; 5 | import com.santarest.http.ByteArrayBody; 6 | import com.santarest.http.HttpBody; 7 | import com.santarest.utils.MimeUtil; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.io.UnsupportedEncodingException; 12 | import java.lang.reflect.Type; 13 | 14 | /** 15 | * A {@link Converter} which uses GSON for serialization and deserialization of entities. 16 | * 17 | */ 18 | public class GsonConverter implements Converter { 19 | private final Gson gson; 20 | private String charset; 21 | 22 | /** 23 | * Create an instance using the supplied {@link Gson} object for conversion. Encoding to JSON and 24 | * decoding from JSON (when no charset is specified by a header) will use UTF-8. 25 | */ 26 | public GsonConverter(Gson gson) { 27 | this(gson, "UTF-8"); 28 | } 29 | 30 | /** 31 | * Create an instance using the supplied {@link Gson} object for conversion. Encoding to JSON and 32 | * decoding from JSON (when no charset is specified by a header) will use the specified charset. 33 | */ 34 | public GsonConverter(Gson gson, String charset) { 35 | this.gson = gson; 36 | this.charset = charset; 37 | } 38 | 39 | @Override 40 | public Object fromBody(HttpBody body, Type type) { 41 | String charset = this.charset; 42 | if (body.mimeType() != null) { 43 | charset = MimeUtil.parseCharset(body.mimeType(), charset); 44 | } 45 | InputStreamReader isr = null; 46 | try { 47 | isr = new InputStreamReader(body.in(), charset); 48 | return gson.fromJson(isr, type); 49 | } catch (JsonParseException e) { 50 | System.err.println("Parse error of " + type + ": " + e.getMessage()); 51 | return null; 52 | } catch (IOException e) { 53 | throw new RuntimeException(e); 54 | } finally { 55 | if (isr != null) { 56 | try { 57 | isr.close(); 58 | } catch (IOException ignored) { 59 | } 60 | } 61 | } 62 | } 63 | 64 | @Override 65 | public HttpBody toBody(Object object) { 66 | try { 67 | return new ByteArrayBody("application/json; charset=" + charset, gson.toJson(object).getBytes(charset)); 68 | } catch (UnsupportedEncodingException e) { 69 | throw new AssertionError(e); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/http/HttpBody.java: -------------------------------------------------------------------------------- 1 | package com.santarest.http; 2 | 3 | import com.santarest.utils.MimeUtil; 4 | 5 | import java.io.ByteArrayInputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.io.UnsupportedEncodingException; 10 | import java.util.Arrays; 11 | 12 | public abstract class HttpBody { 13 | 14 | private final String mimeType; 15 | private byte[] bytes; 16 | 17 | public HttpBody(String mimeType) { 18 | if (mimeType == null) { 19 | mimeType = "application/unknown"; 20 | } 21 | this.mimeType = mimeType; 22 | } 23 | 24 | private byte[] bytes() { 25 | if (bytes == null) { 26 | try { 27 | bytes = getContent(); 28 | if (bytes == null) { 29 | throw new NullPointerException("bytes"); 30 | } 31 | } catch (IOException e) { 32 | e.printStackTrace(); 33 | } 34 | if (bytes == null) bytes = new byte[0]; 35 | } 36 | return bytes; 37 | } 38 | 39 | public abstract byte[] getContent() throws IOException; 40 | 41 | public String fileName() { 42 | return null; 43 | } 44 | 45 | public String mimeType() { 46 | return mimeType; 47 | } 48 | 49 | public long length() { 50 | return bytes().length; 51 | } 52 | 53 | public void writeTo(OutputStream out) throws IOException { 54 | out.write(bytes()); 55 | } 56 | 57 | public InputStream in() throws IOException { 58 | return new ByteArrayInputStream(bytes()); 59 | } 60 | 61 | @Override 62 | public boolean equals(Object o) { 63 | if (this == o) return true; 64 | if (o == null || getClass() != o.getClass()) return false; 65 | 66 | HttpBody that = (HttpBody) o; 67 | 68 | if (!Arrays.equals(bytes(), that.bytes())) return false; 69 | if (!mimeType.equals(that.mimeType)) return false; 70 | 71 | return true; 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | int result = mimeType.hashCode(); 77 | result = 31 * result + Arrays.hashCode(bytes()); 78 | return result; 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | String bodyCharset = MimeUtil.parseCharset(mimeType); 84 | try { 85 | return new String(bytes(), bodyCharset); 86 | } catch (UnsupportedEncodingException e) { 87 | e.printStackTrace(); 88 | } 89 | return "ByteBody[length=" + length() + "]"; 90 | } 91 | } -------------------------------------------------------------------------------- /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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 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 Windowz 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 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/http/FileBody.java: -------------------------------------------------------------------------------- 1 | package com.santarest.http; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.IOException; 7 | 8 | public class FileBody extends HttpBody { 9 | private static final int BUFFER_SIZE = 4096; 10 | 11 | private final String mimeType; 12 | private final File file; 13 | 14 | public FileBody(String mimeType, File file) { 15 | super(mimeType); 16 | if (mimeType == null) { 17 | throw new NullPointerException("mimeType"); 18 | } 19 | if (file == null) { 20 | throw new NullPointerException("file"); 21 | } 22 | this.mimeType = mimeType; 23 | this.file = file; 24 | } 25 | 26 | /** 27 | * Returns the file. 28 | */ 29 | public File file() { 30 | return file; 31 | } 32 | 33 | @Override 34 | public String mimeType() { 35 | return mimeType; 36 | } 37 | 38 | @Override 39 | public long length() { 40 | return file.length(); 41 | } 42 | 43 | @Override 44 | public String fileName() { 45 | return file.getName(); 46 | } 47 | 48 | @Override 49 | public byte[] getContent() throws IOException { 50 | byte[] buffer = new byte[BUFFER_SIZE]; 51 | FileInputStream in = new FileInputStream(file); 52 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 53 | try { 54 | int read; 55 | while ((read = in.read(buffer)) != -1) { 56 | out.write(buffer, 0, read); 57 | } 58 | } finally { 59 | in.close(); 60 | } 61 | return out.toByteArray(); 62 | } 63 | 64 | /** 65 | * Atomically moves the contents of this file to a new location. 66 | * 67 | * @param destination file 68 | * @throws IOException if the move fails 69 | */ 70 | public void moveTo(FileBody destination) throws IOException { 71 | if (!mimeType().equals(destination.mimeType())) { 72 | throw new IOException("Type mismatch."); 73 | } 74 | if (!file.renameTo(destination.file())) { 75 | throw new IOException("Rename failed!"); 76 | } 77 | } 78 | 79 | @Override 80 | public String toString() { 81 | return file.getAbsolutePath() + " (" + mimeType() + ")"; 82 | } 83 | 84 | @Override 85 | public boolean equals(Object o) { 86 | if (this == o) return true; 87 | if (o instanceof FileBody) { 88 | FileBody rhs = (FileBody) o; 89 | return file.equals(rhs.file); 90 | } 91 | return false; 92 | } 93 | 94 | @Override 95 | public int hashCode() { 96 | return file.hashCode(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/validation/RestActionValidators.java: -------------------------------------------------------------------------------- 1 | package com.santarest.validation; 2 | 3 | import com.santarest.RestActionClass; 4 | import com.santarest.annotations.Body; 5 | import com.santarest.annotations.Error; 6 | import com.santarest.annotations.Field; 7 | import com.santarest.annotations.Part; 8 | import com.santarest.annotations.ResponseHeader; 9 | import com.santarest.annotations.RestAction; 10 | import com.santarest.annotations.RestAction.Type; 11 | import com.santarest.annotations.Status; 12 | import com.santarest.http.ByteArrayBody; 13 | import com.santarest.http.FileBody; 14 | import com.santarest.http.FormUrlEncodedRequestBody; 15 | import com.santarest.http.HttpBody; 16 | import com.santarest.http.MultipartRequestBody; 17 | 18 | import java.io.File; 19 | import java.util.ArrayList; 20 | import java.util.HashSet; 21 | import java.util.List; 22 | import java.util.Set; 23 | 24 | /** 25 | * Validate annotations compatibility for classes annotated with 26 | * 27 | * @see RestAction 28 | */ 29 | public class RestActionValidators implements Validator { 30 | 31 | private final List> validators; 32 | 33 | public RestActionValidators() { 34 | validators = new ArrayList>(); 35 | //general rules 36 | validators.add(new FieldsModifiersValidator()); 37 | validators.add(new PathValidator()); 38 | validators.add(new BodyValidator()); 39 | validators.add(new RequestTypeValidator(Body.class, Type.SIMPLE)); 40 | validators.add(new RequestTypeValidator(Field.class, Type.FORM_URL_ENCODED)); 41 | validators.add(new RequestTypeValidator(Part.class, Type.MULTIPART)); 42 | //annotation rules 43 | validators.add(new AnnotationTypesValidator(ResponseHeader.class, String.class)); 44 | validators.add(new AnnotationTypesValidator(Status.class, Boolean.class, Integer.class, Long.class, String.class, boolean.class, int.class, long.class)); 45 | validators.add(new AnnotationTypesValidator(Error.class, Throwable.class, Exception.class)); 46 | validators.add(new AnnotationTypesValidator(Part.class, File.class, byte[].class, String.class, HttpBody.class, 47 | ByteArrayBody.class, MultipartRequestBody.class, FormUrlEncodedRequestBody.class, FileBody.class)); 48 | validators.add(new AnnotationQuantityValidator(Body.class, 1)); 49 | validators.add(new AnnotationQuantityValidator(Field.class, 1)); 50 | 51 | } 52 | 53 | @Override 54 | public Set validate(RestActionClass value) { 55 | Set errors = new HashSet(); 56 | for (Validator validator : validators) { 57 | errors.addAll(validator.validate(value)); 58 | } 59 | return errors; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/validation/PathValidator.java: -------------------------------------------------------------------------------- 1 | package com.santarest.validation; 2 | 3 | import com.santarest.RestActionClass; 4 | import com.santarest.annotations.Path; 5 | import com.santarest.annotations.RestAction; 6 | 7 | import org.apache.commons.lang.StringUtils; 8 | 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Set; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | import javax.lang.model.element.Element; 16 | import javax.lang.model.element.TypeElement; 17 | 18 | /** 19 | * Created by dirong on 6/28/15. 20 | */ 21 | public class PathValidator implements Validator { 22 | 23 | private static final String PATH_FORMAT_DEFINITION = "{%s}"; 24 | private static final Pattern PATH_PATTERN = Pattern.compile("[{](.*?)[}]"); 25 | 26 | @Override 27 | public Set validate(RestActionClass value) { 28 | Set errors = new HashSet(); 29 | TypeElement baseElement = value.getTypeElement(); 30 | if (StringUtils.isEmpty(value.getPath())) { 31 | errors.add(new ValidationError(String.format("Path in @%s for class %s is null or empty! That's not allowed", baseElement, 32 | RestAction.class.getSimpleName(), baseElement.getQualifiedName().toString()), baseElement)); 33 | } 34 | 35 | //Validate that annotated with Path variables exists in path of SaltarAction 36 | List pathAnnotations = value.getAnnotatedElements(Path.class); 37 | for (Element element : pathAnnotations) { 38 | Path annotation = element.getAnnotation(Path.class); 39 | String formatedPath = String.format(PATH_FORMAT_DEFINITION, annotation.value()); 40 | if (value.getPath().contains(formatedPath)) continue; 41 | errors.add(new ValidationError(String.format("%s annotated variable doesn't exist in your path", element, Path.class.getName()), baseElement)); 42 | } 43 | 44 | //Validate that specified variable in path, has specified right annotated variable in class 45 | Matcher matcher = PATH_PATTERN.matcher(value.getPath()); 46 | while (matcher.find()) { 47 | boolean hasAnnotatedVariable = false; 48 | String group = matcher.group(1); 49 | for (Element element : pathAnnotations) { 50 | Path annotation = element.getAnnotation(Path.class); 51 | if (annotation.value().equals(group)) { 52 | hasAnnotatedVariable = true; 53 | break; 54 | } 55 | } 56 | if (!hasAnnotatedVariable) { 57 | errors.add(new ValidationError(String.format("Annotate varaible with %s annotation with value \"%s\"", baseElement, Path.class.getName(), group), baseElement)); 58 | } 59 | } 60 | return errors; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/Utils.java: -------------------------------------------------------------------------------- 1 | package com.santarest; 2 | 3 | import com.santarest.http.ByteArrayBody; 4 | import com.santarest.http.HttpBody; 5 | import com.santarest.http.Request; 6 | import com.santarest.http.Response; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | 12 | final class Utils { 13 | private static final int BUFFER_SIZE = 0x1000; 14 | 15 | /** 16 | * Creates a {@code byte[]} from reading the entirety of an {@link InputStream}. May return an 17 | * empty array but never {@code null}. 18 | *

19 | * Copied from Guava's {@code ByteStreams} class. 20 | */ 21 | static byte[] streamToBytes(InputStream stream) throws IOException { 22 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 23 | if (stream != null) { 24 | byte[] buf = new byte[BUFFER_SIZE]; 25 | int r; 26 | while ((r = stream.read(buf)) != -1) { 27 | baos.write(buf, 0, r); 28 | } 29 | } 30 | return baos.toByteArray(); 31 | } 32 | 33 | /** 34 | * Conditionally replace a {@link Request} with an identical copy whose body is backed by a 35 | * byte[] rather than an input stream. 36 | */ 37 | static Request readBodyToBytesIfNecessary(Request request) throws IOException { 38 | HttpBody body = request.getBody(); 39 | if (body == null || body instanceof ByteArrayBody) { 40 | return request; 41 | } 42 | 43 | String bodyMime = body.mimeType(); 44 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 45 | body.writeTo(baos); 46 | body = new ByteArrayBody(bodyMime, baos.toByteArray()); 47 | 48 | return new Request(request.getMethod(), request.getUrl(), request.getHeaders(), body); 49 | } 50 | 51 | /** 52 | * Conditionally replace a {@link Response} with an identical copy whose body is backed by a 53 | * byte[] rather than an input stream. 54 | */ 55 | static Response readBodyToBytesIfNecessary(Response response) throws IOException { 56 | HttpBody body = response.getBody(); 57 | if (body == null || body instanceof ByteArrayBody) { 58 | return response; 59 | } 60 | 61 | String bodyMime = body.mimeType(); 62 | InputStream is = body.in(); 63 | try { 64 | byte[] bodyBytes = Utils.streamToBytes(is); 65 | body = new ByteArrayBody(bodyMime, bodyBytes); 66 | 67 | return replaceResponseBody(response, body); 68 | } finally { 69 | if (is != null) { 70 | try { 71 | is.close(); 72 | } catch (IOException ignored) { 73 | } 74 | } 75 | } 76 | } 77 | 78 | static Response replaceResponseBody(Response response, HttpBody body) { 79 | return new Response(response.getUrl(), response.getStatus(), response.getReason(), 80 | response.getHeaders(), body); 81 | } 82 | 83 | private Utils() { 84 | // No instances. 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/TypeUtils.java: -------------------------------------------------------------------------------- 1 | package com.santarest; 2 | 3 | import com.google.gson.reflect.TypeToken; 4 | import com.squareup.javapoet.TypeName; 5 | 6 | import java.lang.reflect.ParameterizedType; 7 | import java.lang.reflect.Type; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | import javax.lang.model.element.Element; 12 | 13 | public class TypeUtils { 14 | public static boolean equalTypes(Element element, TypeToken token) { 15 | return equalTypes(element, token.getType()); 16 | } 17 | 18 | /** 19 | * if type is parameterized and one generic parameter contains "?", this will means that any type acceptable 20 | */ 21 | public static boolean equalTypes(Element element, Type type) { 22 | String elementClassName = TypeName.get(element.asType()).toString(); 23 | if (type instanceof ParameterizedType) { 24 | List generics = Arrays.asList(((ParameterizedType) type).getActualTypeArguments()); 25 | List elementGenerics = Arrays.asList(elementClassName.replaceAll(".*?[<]|[>]", "").split(",")); 26 | return getElementTypeName(element).equals(getTypeName(type)) && isGenericsEquals(generics, elementGenerics); 27 | } 28 | return TypeName.get(element.asType()).equals(TypeName.get(type)); 29 | } 30 | 31 | public static boolean isPrimitive(Element element) { 32 | return TypeName.get(element.asType()).isPrimitive(); 33 | } 34 | 35 | private static boolean isGenericsEquals(List generics, List elementGenerics) { 36 | if (generics.size() != elementGenerics.size()) { 37 | return false; 38 | } 39 | boolean genericsEquals = true; 40 | for (int i = 0; i < elementGenerics.size(); i++) { 41 | String genericTypeName = TypeName.get(generics.get(i)).toString(); 42 | String elementGenericName = elementGenerics.get(i).trim(); 43 | boolean anyType = genericTypeName.contains("?"); 44 | if (!elementGenericName.equals(genericTypeName) && !anyType) { 45 | genericsEquals = false; 46 | break; 47 | } 48 | } 49 | return genericsEquals; 50 | } 51 | 52 | private static String getTypeName(Type type) { 53 | return TypeName.get(type).toString().replaceAll("[<].*?[>]", ""); 54 | } 55 | 56 | private static String getElementTypeName(Element element) { 57 | return TypeName.get(element.asType()).toString().replaceAll("[<].*?[>]", ""); 58 | } 59 | 60 | public static boolean containsType(Element element, Type... classes) { 61 | for (Type clazz : classes) { 62 | if (equalTypes(element, clazz)) { 63 | return true; 64 | } 65 | } 66 | return false; 67 | } 68 | 69 | public static Class getElementClass(Element element) { 70 | try { 71 | return Class.forName(TypeName.get(element.asType()).toString().replaceAll("[<].*?[>]", "")); 72 | } catch (ClassNotFoundException e) { 73 | e.printStackTrace(); 74 | } 75 | return null; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/SantaProcessor.java: -------------------------------------------------------------------------------- 1 | package com.santarest; 2 | 3 | import com.google.auto.service.AutoService; 4 | 5 | import com.santarest.annotations.RestAction; 6 | import com.santarest.validation.ClassValidator; 7 | import com.santarest.validation.RestActionValidators; 8 | import com.santarest.validation.ValidationError; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collection; 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | 15 | import javax.annotation.processing.AbstractProcessor; 16 | import javax.annotation.processing.Filer; 17 | import javax.annotation.processing.Messager; 18 | import javax.annotation.processing.ProcessingEnvironment; 19 | import javax.annotation.processing.Processor; 20 | import javax.annotation.processing.RoundEnvironment; 21 | import javax.lang.model.SourceVersion; 22 | import javax.lang.model.element.Element; 23 | import javax.lang.model.element.TypeElement; 24 | import javax.lang.model.util.Elements; 25 | import javax.tools.Diagnostic; 26 | 27 | @AutoService(Processor.class) 28 | //@SupportedAnnotationTypes({"com.santarest.RestAction"}) 29 | public class SantaProcessor extends AbstractProcessor { 30 | private Elements elementUtils; 31 | private Messager messager; 32 | private ClassValidator classValidator; 33 | private RestActionValidators restActionValidators; 34 | private FactoryGenerator factoryGenerator; 35 | private HelpersGenerator helpersGenerator; 36 | 37 | @Override 38 | public synchronized void init(ProcessingEnvironment processingEnv) { 39 | super.init(processingEnv); 40 | elementUtils = processingEnv.getElementUtils(); 41 | messager = processingEnv.getMessager(); 42 | classValidator = new ClassValidator(); 43 | restActionValidators = new RestActionValidators(); 44 | Filer filer = processingEnv.getFiler(); 45 | factoryGenerator = new FactoryGenerator(filer); 46 | helpersGenerator = new HelpersGenerator(filer); 47 | } 48 | 49 | @Override 50 | public Set getSupportedAnnotationTypes() { 51 | Set annotataions = new HashSet(); 52 | annotataions.add(RestAction.class.getCanonicalName()); 53 | return annotataions; 54 | } 55 | 56 | @Override 57 | public SourceVersion getSupportedSourceVersion() { 58 | return SourceVersion.latestSupported(); 59 | } 60 | 61 | @Override 62 | public boolean process(Set annotations, RoundEnvironment roundEnv) { 63 | if(annotations.isEmpty()) return true; 64 | ArrayList actionClasses = new ArrayList(); 65 | for (Element saltarElement : roundEnv.getElementsAnnotatedWith(RestAction.class)) { 66 | Set errors = new HashSet(); 67 | errors.addAll(classValidator.validate(saltarElement)); 68 | if (!errors.isEmpty()) { 69 | printErrors(errors); 70 | continue; 71 | } 72 | TypeElement typeElement = (TypeElement) saltarElement; 73 | RestActionClass actionClass = new RestActionClass(elementUtils, typeElement); 74 | errors.addAll(restActionValidators.validate(actionClass)); 75 | if (!errors.isEmpty()) { 76 | printErrors(errors); 77 | continue; 78 | } 79 | actionClasses.add(actionClass); 80 | } 81 | if (!actionClasses.isEmpty()) { 82 | helpersGenerator.generate(actionClasses); 83 | } 84 | factoryGenerator.generate(actionClasses); 85 | return true; 86 | } 87 | 88 | private void printErrors(Collection errors) { 89 | for (ValidationError error : errors) { 90 | messager.printMessage(Diagnostic.Kind.ERROR, error.getMessage(), error.getElement()); 91 | } 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /sample-jar/src/main/java/com/santarest/sample/SamplesRunner.java: -------------------------------------------------------------------------------- 1 | package com.santarest.sample; 2 | 3 | import com.santarest.RequestBuilder; 4 | import com.santarest.SantaRest; 5 | import com.santarest.http.Request; 6 | import com.santarest.http.Response; 7 | import com.squareup.otto.Subscribe; 8 | 9 | import java.util.concurrent.Executors; 10 | 11 | import rx.functions.Action1; 12 | import rx.schedulers.Schedulers; 13 | 14 | /** 15 | * Created by vladla on 11/13/15. 16 | */ 17 | public class SamplesRunner { 18 | 19 | private SantaRest githubRest; 20 | private SantaRest uploadFileServer; 21 | 22 | public SamplesRunner() { 23 | githubRest = new SantaRest.Builder() 24 | .setServerUrl("https://api.github.com") 25 | .addRequestInterceptor(new SantaRest.RequestInterceptor() { 26 | @Override 27 | public void intercept(RequestBuilder request) { 28 | request.addHeader("test", "test"); 29 | } 30 | }) 31 | .addResponseInterceptor(new SantaRest.ResponseListener() { 32 | @Override 33 | public void onResponseReceived(Object action, Request request, Response response) { 34 | System.out.println(request); 35 | System.out.println(response); 36 | } 37 | 38 | }) 39 | .build(); 40 | uploadFileServer = new SantaRest.Builder() 41 | .setServerUrl("http://posttestserver.com") 42 | .addRequestInterceptor(new SantaRest.RequestInterceptor() { 43 | @Override 44 | public void intercept(RequestBuilder request) { 45 | request.addHeader("test", "test"); 46 | } 47 | }) 48 | .addResponseInterceptor(new SantaRest.ResponseListener() { 49 | @Override 50 | public void onResponseReceived(Object action, Request request, Response response) { 51 | System.out.println(response); 52 | } 53 | }) 54 | .build(); 55 | } 56 | 57 | public void runTests() { 58 | uploadFileServer.sendAction(new UploadFileAction()); 59 | githubRest.sendAction(new ExampleAction("square", "otto")); 60 | githubRest.sendAction(new OuterAction.InnerAction()); 61 | githubRest.createObservable(new ExampleAction("santagroup", "santarest")) 62 | .subscribeOn(Schedulers.io()) 63 | .observeOn(Schedulers.from(Executors.newSingleThreadExecutor())) 64 | .subscribe(new Action1() { 65 | @Override 66 | public void call(ExampleAction exampleAction) { 67 | System.out.println(exampleAction); 68 | } 69 | }, new Action1() { 70 | @Override 71 | public void call(Throwable throwable) { 72 | throwable.printStackTrace(); 73 | } 74 | }); 75 | } 76 | 77 | public void registerEvents() { 78 | githubRest.subscribe(this); 79 | uploadFileServer.subscribe(this); 80 | } 81 | 82 | public void unregisterEvents() { 83 | githubRest.unsubscribe(this); 84 | uploadFileServer.unsubscribe(this); 85 | } 86 | 87 | @SuppressWarnings("unused") 88 | @Subscribe 89 | public void onExampleAction(ExampleAction action) { 90 | System.out.println(action); 91 | System.out.println(action.success); 92 | System.out.println(action.isSuccess()); 93 | } 94 | 95 | @SuppressWarnings("unused") 96 | @Subscribe 97 | public void onUploadFileAction(UploadFileAction action) { 98 | System.out.println(action); 99 | System.out.println(action.success); 100 | System.out.println("response = " + action.getResponse()); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/client/UrlConnectionClient.java: -------------------------------------------------------------------------------- 1 | package com.santarest.client; 2 | 3 | import com.santarest.http.ByteArrayBody; 4 | import com.santarest.http.Header; 5 | import com.santarest.http.HttpBody; 6 | import com.santarest.http.Request; 7 | import com.santarest.http.Response; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.net.HttpURLConnection; 13 | import java.net.URL; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | 19 | /** 20 | * Retrofit client that uses {@link HttpURLConnection} for communication. 21 | */ 22 | public class UrlConnectionClient implements HttpClient { 23 | private static final int CHUNK_SIZE = 4096; 24 | private static final int BUFFER_SIZE = 0x1000; 25 | 26 | public UrlConnectionClient() { 27 | } 28 | 29 | @Override 30 | public Response execute(Request request) throws IOException { 31 | HttpURLConnection connection = openConnection(request); 32 | prepareRequest(connection, request); 33 | return readResponse(connection); 34 | } 35 | 36 | protected HttpURLConnection openConnection(Request request) throws IOException { 37 | HttpURLConnection connection = (HttpURLConnection) new URL(request.getUrl()).openConnection(); 38 | connection.setConnectTimeout(CONNECT_TIMEOUT_MILLIS); 39 | connection.setReadTimeout(READ_TIMEOUT_MILLIS); 40 | return connection; 41 | } 42 | 43 | private void prepareRequest(HttpURLConnection connection, Request request) throws IOException { 44 | connection.setRequestMethod(request.getMethod()); 45 | connection.setDoInput(true); 46 | 47 | for (Header header : request.getHeaders()) { 48 | connection.addRequestProperty(header.getName(), header.getValue()); 49 | } 50 | 51 | HttpBody body = request.getBody(); 52 | if (body != null) { 53 | connection.setDoOutput(true); 54 | connection.addRequestProperty("Content-Type", body.mimeType()); 55 | long length = body.length(); 56 | if (length != -1) { 57 | connection.setFixedLengthStreamingMode((int) length); 58 | connection.addRequestProperty("Content-Length", String.valueOf(length)); 59 | } else { 60 | connection.setChunkedStreamingMode(CHUNK_SIZE); 61 | } 62 | body.writeTo(connection.getOutputStream()); 63 | } 64 | } 65 | 66 | Response readResponse(HttpURLConnection connection) throws IOException { 67 | int status = connection.getResponseCode(); 68 | String reason = connection.getResponseMessage(); 69 | if (reason == null) reason = ""; // HttpURLConnection treats empty reason as null. 70 | 71 | List

headers = new ArrayList
(); 72 | for (Map.Entry> field : connection.getHeaderFields().entrySet()) { 73 | String name = field.getKey(); 74 | for (String value : field.getValue()) { 75 | headers.add(new Header(name, value)); 76 | } 77 | } 78 | 79 | String mimeType = connection.getContentType(); 80 | InputStream stream; 81 | if (status >= 400) { 82 | stream = connection.getErrorStream(); 83 | } else { 84 | stream = connection.getInputStream(); 85 | } 86 | ByteArrayBody responseBody = new ByteArrayBody(mimeType, streamToBytes(stream)); 87 | return new Response(connection.getURL().toString(), status, reason, headers, responseBody); 88 | } 89 | 90 | public static byte[] streamToBytes(InputStream stream) throws IOException { 91 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 92 | if (stream != null) { 93 | byte[] buf = new byte[BUFFER_SIZE]; 94 | int r; 95 | while ((r = stream.read(buf)) != -1) { 96 | baos.write(buf, 0, r); 97 | } 98 | } 99 | return baos.toByteArray(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/RestActionClass.java: -------------------------------------------------------------------------------- 1 | package com.santarest; 2 | 3 | import com.santarest.annotations.RestAction; 4 | import com.santarest.annotations.RestAction.Method; 5 | import com.squareup.javapoet.TypeName; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | 12 | import javax.lang.model.element.Element; 13 | import javax.lang.model.element.ElementKind; 14 | import javax.lang.model.element.Name; 15 | import javax.lang.model.element.PackageElement; 16 | import javax.lang.model.element.TypeElement; 17 | import javax.lang.model.util.Elements; 18 | 19 | public class RestActionClass { 20 | private final Method method; 21 | private final String path; 22 | private final RestAction.Type requestType; 23 | private final TypeElement typeElement; 24 | private final Elements elementUtils; 25 | private final List allAnnotatedMembers; 26 | private final HashMap> allAnnotatedMembersMap; 27 | 28 | public RestActionClass(Elements elementUtils, TypeElement typeElement) { 29 | RestAction annotation = typeElement.getAnnotation(RestAction.class); 30 | this.typeElement = typeElement; 31 | this.elementUtils = elementUtils; 32 | method = annotation.method(); 33 | path = annotation.value(); 34 | requestType = annotation.type(); 35 | 36 | allAnnotatedMembersMap = new HashMap>(); 37 | allAnnotatedMembers = new ArrayList(); 38 | List libraryAnnotations = getLibraryAnnotations(); 39 | 40 | for (Element element : elementUtils.getAllMembers(typeElement)) { 41 | for (Class libraryAnnotation : libraryAnnotations) { 42 | if (element.getKind() != ElementKind.FIELD || element.getAnnotation(libraryAnnotation) == null) { 43 | continue; 44 | } 45 | allAnnotatedMembers.add(element); 46 | 47 | if (!allAnnotatedMembersMap.containsKey(libraryAnnotation)) { 48 | allAnnotatedMembersMap.put(libraryAnnotation, new ArrayList()); 49 | } 50 | allAnnotatedMembersMap.get(libraryAnnotation).add(element); 51 | } 52 | } 53 | } 54 | 55 | public Method getMethod() { 56 | return method; 57 | } 58 | 59 | public String getPath() { 60 | return path; 61 | } 62 | 63 | public RestAction.Type getRequestType() { 64 | return requestType; 65 | } 66 | 67 | public TypeElement getTypeElement() { 68 | return typeElement; 69 | } 70 | 71 | public TypeName getTypeName() { 72 | return TypeName.get(getTypeElement().asType()); 73 | } 74 | 75 | public String getHelperName() { 76 | return getTypeElement().getSimpleName() + HelpersGenerator.HELPER_SUFFIX; 77 | } 78 | 79 | public String getFullHelperName() { 80 | return getPackageName() + "." + getTypeElement().getSimpleName() + HelpersGenerator.HELPER_SUFFIX; 81 | } 82 | 83 | public String getPackageName() { 84 | Name qualifiedName = elementUtils.getPackageOf(getTypeElement()).getQualifiedName(); 85 | return qualifiedName.toString(); 86 | } 87 | 88 | public List getAnnotatedElements(Class annotationClass) { 89 | List elements = allAnnotatedMembersMap.get(annotationClass); 90 | if (elements == null) return Collections.emptyList(); 91 | return elements; 92 | } 93 | 94 | public List getAllAnnotatedMembers() { 95 | return allAnnotatedMembers; 96 | } 97 | 98 | private List getLibraryAnnotations() { 99 | String annotationPackage = RestAction.class.getPackage().getName(); 100 | PackageElement packageElement = elementUtils.getPackageElement(annotationPackage); 101 | List libraryAnnotation = new ArrayList(); 102 | for (Element element : packageElement.getEnclosedElements()) { 103 | if (element.getKind() != ElementKind.ANNOTATION_TYPE) continue; 104 | try { 105 | libraryAnnotation.add(Class.forName(element.asType().toString())); 106 | } catch (ClassNotFoundException e) { 107 | e.printStackTrace(); 108 | } 109 | } 110 | return libraryAnnotation; 111 | } 112 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SantaRest 2 | 3 | Flexible library to ease HTTP/HTTPS requests execution. It can be used for both Android and Java. 4 | 5 | ### What does SantaRest give? 6 | 7 | 1. Flexibility and easy usage (thanks to [Retrofit](http://square.github.io/retrofit/)) 8 | 2. Networking code and responses handling decoupling (thanks to [RXJava](https://github.com/ReactiveX/RxJava), [Otto](http://square.github.io/otto/) and [EventBus](https://github.com/greenrobot/EventBus)) 9 | 10 | With the help of SantaRest you can create application with network communication but without callbacks and Android activity's life-cycle checking. 11 | By relying on compile-time annotation processor that generates code for you, you can write clear maintainable code. 12 | 13 | ### Usage 14 | 15 | To send and receive HTTP requests you should use SantaRest class 16 | ```java 17 | santaRest = new SantaRest.Builder() 18 | .setServerUrl("https://api.github.com") 19 | .build(); 20 | ``` 21 | 22 | Each HTTP request in SantaRest is an individual class that contains all information about the request and response. Let's call it as Action. 23 | 24 | You should annotate action class with `@RestAction`. 25 | ```java 26 | @RestAction(value = "/demo", method = RestAction.Method.GET) 27 | public class ExampleAction { 28 | 29 | } 30 | ``` 31 | 32 | To configure request, you can annotate Action fields with: 33 | * `@Path` for path value 34 | * `@Query` for request URL parameters 35 | * `@Body` for POST request body 36 | * `@RequestHeader` for request headers 37 | * `@Field` for request fields if request type is `RestAction.Type.FORM_URL_ENCODED` 38 | * `@Part` for multipart request parts 39 | 40 | To process response, you may use special annotations: 41 | * `@Response` for getting response body. 42 | * `@Status` for getting response status. You can use `Integer`, `Long`, `int` or `long` fields to get status code or use `boolean` if you want to know if request was sent successfully 43 | * `@ResponseHeader` for getting response headers 44 | 45 | ```java 46 | @RestAction(value = "/demo/{examplePath}/info", 47 | type = RestAction.Type.SIMPLE, 48 | method = RestAction.Method.GET) 49 | public class ExampleAction { 50 | @Path("examplePath") 51 | String ownerr; 52 | @Query("repo") 53 | int query; 54 | @RequestHeader("Example-Header-Name") 55 | String requestHeaderValue; 56 | @Status 57 | int statusCode; 58 | @Body 59 | ExampleModel exampleModel; 60 | @Response 61 | ExampleDataModel exampleDataModel; 62 | @ResponseHeader("Example-Responseheader-Name") 63 | String responseHeaderValue; 64 | } 65 | ``` 66 | 67 | To send actions asynchronously, you should use method `sendAction`. For synchronous calls use `runAction`. 68 | ```java 69 | santaRest.sendAction(new ExampleAction()); 70 | santaRest.runAction(new ExampleAction()); 71 | ``` 72 | 73 | To receive actions with responses, you should subscribe to events: 74 | ```java 75 | santaRest.getActionPoster().subscribe(this); 76 | ``` 77 | Don’t forget to unsubscribe by using: 78 | ```java 79 | santaRest.getActionPoster().unsubscribe(this); 80 | ``` 81 | 82 | For Android, we recommend you to use `santaRest.subscribe()` and `santaRest.unsubscribe(this)` in `onResume` and `onPause` lifecycle callbacks. 83 | 84 | Also, you can receive actions using method `observeActions`. This method returns `rx.Observable` to subscribe on all actions what will completed. 85 | 86 | ```java 87 | santaRest.observeActions() 88 | .ofType(ExampleAction.class) 89 | .filter((action) -> exampleAction.success) 90 | .subscribe((action) -> updateUI()); 91 | ``` 92 | 93 | ### Converters 94 | It is possible to add converters. By default, SantaRest works with `GsonConverter`. But you can create your own. Just implement `Converter` interface. 95 | ```java 96 | SantaRest santaRest = new SantaRest.Builder() 97 | .setConverter(new GsonConverter(gson)) 98 | .build(); 99 | ``` 100 | 101 | ### Proguard 102 | Like in cases with all libraries that generate dynamic code, Proguard might think some classes are unused and removes them. To prevent this, the following lines should be added to your proguard config file. 103 | 104 | ```java 105 | -keep class **$$ActionHelperFactoryImpl { *; } 106 | ``` 107 | 108 | ### Download 109 | [ ![Download](https://api.bintray.com/packages/santagroup/maven/santarest/images/download.svg) ](https://bintray.com/santagroup/maven/santarest/_latestVersion) 110 | 111 | ```groovy 112 | buildscript { 113 | repositories { 114 | jcenter() 115 | } 116 | dependencies { 117 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' 118 | } 119 | } 120 | apply plugin: 'com.neenbedankt.android-apt' 121 | 122 | dependencies { 123 | compile 'com.github.santagroup:santarest:0.0.2' 124 | apt 'com.github.santagroup:santarest-compiler:0.0.2' 125 | } 126 | ``` 127 | -------------------------------------------------------------------------------- /sample-android/src/main/java/com/santarest/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.santarest.sample; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.support.v7.app.ActionBarActivity; 6 | 7 | import com.santarest.RequestBuilder; 8 | import com.santarest.SantaRest; 9 | import com.santarest.http.Request; 10 | import com.santarest.http.Response; 11 | import com.squareup.otto.Subscribe; 12 | 13 | import java.util.concurrent.Executor; 14 | 15 | import rx.Observable; 16 | import rx.functions.Action1; 17 | import rx.schedulers.Schedulers; 18 | 19 | public class MainActivity extends ActionBarActivity { 20 | 21 | private SantaRest githubRest; 22 | private SantaRest uploadFileServer; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(com.santarest.sample.R.layout.activity_main); 28 | githubRest = new SantaRest.Builder() 29 | .setServerUrl("https://api.github.com") 30 | .addRequestInterceptor(new SantaRest.RequestInterceptor() { 31 | @Override 32 | public void intercept(RequestBuilder request) { 33 | request.addHeader("test", "test"); 34 | } 35 | }) 36 | .addResponseInterceptor(new SantaRest.ResponseListener() { 37 | @Override 38 | public void onResponseReceived(Object action, Request request, Response response) { 39 | System.out.println(request); 40 | System.out.println(response); 41 | } 42 | 43 | }) 44 | .build(); 45 | uploadFileServer = new SantaRest.Builder() 46 | .setServerUrl("http://posttestserver.com") 47 | .addRequestInterceptor(new SantaRest.RequestInterceptor() { 48 | @Override 49 | public void intercept(RequestBuilder request) { 50 | request.addHeader("test", "test"); 51 | } 52 | }) 53 | .addResponseInterceptor(new SantaRest.ResponseListener() { 54 | @Override 55 | public void onResponseReceived(Object action, Request request, Response response) { 56 | System.out.println(response); 57 | } 58 | }) 59 | .build(); 60 | uploadFileServer.sendAction(new UploadFileAction()); 61 | githubRest.observeActions() 62 | .ofType(ExampleAction.class) 63 | .subscribe(new Action1() { 64 | @Override 65 | public void call(ExampleAction exampleAction) { 66 | System.out.println("exampleAction = [" + exampleAction + "]"); 67 | } 68 | }); 69 | githubRest.sendAction(new ExampleAction("square", "otto")); 70 | githubRest.sendAction(new OuterAction.InnerAction()); 71 | githubRest.createObservable(new ExampleAction("santagroup", "santarest")) 72 | .subscribeOn(Schedulers.io()) 73 | .observeOn(Schedulers.from(new Executor() { 74 | Handler handler = new Handler(); 75 | 76 | @Override 77 | public void execute(Runnable command) { 78 | handler.post(command); 79 | } 80 | })) 81 | .subscribe(new Action1() { 82 | @Override 83 | public void call(ExampleAction exampleAction) { 84 | System.out.println(exampleAction); 85 | } 86 | }, new Action1() { 87 | @Override 88 | public void call(Throwable throwable) { 89 | throwable.printStackTrace(); 90 | } 91 | }); 92 | } 93 | 94 | 95 | @Override 96 | protected void onResume() { 97 | super.onResume(); 98 | githubRest.getActionPoster().subscribe(this); 99 | uploadFileServer.getActionPoster().subscribe(this); 100 | } 101 | 102 | @Override 103 | protected void onPause() { 104 | super.onPause(); 105 | githubRest.getActionPoster().unsubscribe(this); 106 | uploadFileServer.getActionPoster().unsubscribe(this); 107 | } 108 | 109 | @Subscribe 110 | public void onExampleAction(ExampleAction action) { 111 | System.out.println(action); 112 | System.out.println(action.success); 113 | System.out.println(action.isSuccess()); 114 | } 115 | 116 | @Subscribe 117 | public void onUploadFileAction(UploadFileAction action) { 118 | System.out.println(action); 119 | System.out.println(action.success); 120 | System.out.println("response = " + action.getResponse()); 121 | } 122 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 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 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/Defaults.java: -------------------------------------------------------------------------------- 1 | package com.santarest; 2 | 3 | import android.os.Build; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.os.Process; 7 | import android.util.Log; 8 | 9 | import com.google.gson.Gson; 10 | import com.santarest.callback.EventBusPoster; 11 | import com.santarest.callback.OttoPoster; 12 | import com.santarest.client.AndroidApacheClient; 13 | import com.santarest.client.HttpClient; 14 | import com.santarest.client.OkClient; 15 | import com.santarest.client.UrlConnectionClient; 16 | import com.santarest.converter.Converter; 17 | import com.santarest.converter.GsonConverter; 18 | import com.santarest.utils.Logger; 19 | 20 | import java.util.concurrent.Executor; 21 | import java.util.concurrent.Executors; 22 | import java.util.concurrent.ThreadFactory; 23 | 24 | import static java.lang.Thread.MIN_PRIORITY; 25 | 26 | /** 27 | * Created by dirong on 6/18/15. 28 | */ 29 | class Defaults { 30 | 31 | private static final String LOG_TAG = "SantaRest"; 32 | 33 | static final String THREAD_PREFIX = LOG_TAG + "-"; 34 | static final String IDLE_THREAD_NAME = THREAD_PREFIX + "Idle"; 35 | 36 | private enum Platform { 37 | ANDROID, BASE 38 | } 39 | 40 | private static class SynchronousExecutor implements Executor { 41 | @Override 42 | public void execute(Runnable runnable) { 43 | runnable.run(); 44 | } 45 | } 46 | 47 | private static final class MainThreadExecutor implements Executor { 48 | private final Handler handler = new Handler(Looper.getMainLooper()); 49 | 50 | @Override 51 | public void execute(Runnable r) { 52 | handler.post(r); 53 | } 54 | } 55 | 56 | private static Platform platform; 57 | 58 | private static Platform getPlatform() { 59 | if (platform == null) { 60 | platform = findPlatform(); 61 | } 62 | return platform; 63 | } 64 | 65 | private static Platform findPlatform() { 66 | try { 67 | Class.forName("android.os.Build"); 68 | if (Build.VERSION.SDK_INT != 0) { 69 | return Platform.ANDROID; 70 | } 71 | } catch (ClassNotFoundException ignored) { 72 | } 73 | 74 | return Platform.BASE; 75 | } 76 | 77 | private static boolean isPlatform(Platform platform) { 78 | return getPlatform() == platform; 79 | } 80 | 81 | static Converter getConverter() { 82 | return new GsonConverter(new Gson()); 83 | } 84 | 85 | static HttpClient getClient() { 86 | final HttpClient client; 87 | if (hasOkHttpOnClasspath()) { 88 | client = new OkClient(); 89 | } else if (isPlatform(Platform.ANDROID) && Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { 90 | client = new AndroidApacheClient(); 91 | } else { 92 | client = new UrlConnectionClient(); 93 | } 94 | return client; 95 | } 96 | 97 | static Executor getDefaultHttpExecutor() { 98 | return Executors.newCachedThreadPool(new ThreadFactory() { 99 | @Override 100 | public Thread newThread(final Runnable r) { 101 | return new Thread(new Runnable() { 102 | @Override 103 | public void run() { 104 | if (isPlatform(Platform.ANDROID)) { 105 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 106 | } else { 107 | Thread.currentThread().setPriority(MIN_PRIORITY); 108 | } 109 | r.run(); 110 | } 111 | }, IDLE_THREAD_NAME); 112 | } 113 | }); 114 | } 115 | 116 | public static Executor getDefaultCallbackExecutor() { 117 | if (isPlatform(Platform.ANDROID)) { 118 | return new MainThreadExecutor(); 119 | } 120 | return new SynchronousExecutor(); 121 | } 122 | 123 | public static ActionPoster getDefualtActionPoster() { 124 | if (isPlatform(Platform.ANDROID)) { 125 | if (hasClass("de.greenrobot.event.EventBus")) { 126 | return new EventBusPoster(); 127 | } 128 | if (hasClass("com.squareup.otto.Bus")) { 129 | return new OttoPoster(); 130 | } 131 | } 132 | return null; 133 | } 134 | 135 | private static boolean hasClass(String className) { 136 | boolean has = false; 137 | try { 138 | Class.forName(className); 139 | has = true; 140 | } catch (ClassNotFoundException e) { 141 | } 142 | return has; 143 | } 144 | 145 | private static boolean hasOkHttpOnClasspath() { 146 | if (hasClass("com.squareup.okhttp.OkUrlFactory") 147 | || hasClass("com.squareup.okhttp.OkHttpClient")) { 148 | return true; 149 | } 150 | return false; 151 | } 152 | 153 | static Logger getLogger() { 154 | if (isPlatform(Platform.ANDROID)) { 155 | return new Logger() { 156 | @Override 157 | public void log(String message, String... args) { 158 | Log.d(LOG_TAG, String.format(message, args)); 159 | } 160 | 161 | @Override 162 | public void error(String message, String... args) { 163 | Log.e(LOG_TAG, String.format(message, args)); 164 | } 165 | }; 166 | } else { 167 | return new Logger() { 168 | 169 | private String formatMessage(String message, String... args) { 170 | StringBuilder sb = new StringBuilder(LOG_TAG); 171 | sb.append(": "); 172 | sb.append(String.format(message, args)); 173 | return sb.toString(); 174 | } 175 | 176 | @Override 177 | public void log(String message, String... args) { 178 | System.out.println(formatMessage(message, args)); 179 | } 180 | 181 | @Override 182 | public void error(String message, String... args) { 183 | System.err.println(formatMessage(message, args)); 184 | } 185 | }; 186 | } 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/http/MultipartRequestBody.java: -------------------------------------------------------------------------------- 1 | package com.santarest.http; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | import java.util.ArrayList; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | import java.util.UUID; 10 | 11 | public final class MultipartRequestBody extends HttpBody { 12 | public static final String DEFAULT_TRANSFER_ENCODING = "binary"; 13 | 14 | private static final class MimePart { 15 | private final HttpBody body; 16 | private final String name; 17 | private final String transferEncoding; 18 | private final boolean isFirst; 19 | private final String boundary; 20 | 21 | private byte[] partBoundary; 22 | private byte[] partHeader; 23 | private boolean isBuilt; 24 | 25 | public MimePart(String name, String transferEncoding, HttpBody body, String boundary, 26 | boolean isFirst) { 27 | this.name = name; 28 | this.transferEncoding = transferEncoding; 29 | this.body = body; 30 | this.isFirst = isFirst; 31 | this.boundary = boundary; 32 | } 33 | 34 | public void writeTo(OutputStream out) throws IOException { 35 | build(); 36 | out.write(partBoundary); 37 | out.write(partHeader); 38 | body.writeTo(out); 39 | } 40 | 41 | public long size() { 42 | build(); 43 | if (body.length() > -1) { 44 | return body.length() + partBoundary.length + partHeader.length; 45 | } else { 46 | return -1; 47 | } 48 | } 49 | 50 | private void build() { 51 | if (isBuilt) return; 52 | partBoundary = buildBoundary(boundary, isFirst, false); 53 | partHeader = buildHeader(name, transferEncoding, body); 54 | isBuilt = true; 55 | } 56 | } 57 | 58 | private final List mimeParts = new LinkedList(); 59 | 60 | private final byte[] footer; 61 | private final String boundary; 62 | private long length; 63 | 64 | public MultipartRequestBody() { 65 | this(UUID.randomUUID().toString()); 66 | } 67 | 68 | MultipartRequestBody(String boundary) { 69 | super("multipart/form-data; boundary=" + boundary); 70 | this.boundary = boundary; 71 | footer = buildBoundary(boundary, false, true); 72 | length = footer.length; 73 | } 74 | 75 | @Override 76 | public byte[] getContent() { 77 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 78 | try { 79 | for (MimePart part : mimeParts) { 80 | part.writeTo(out); 81 | } 82 | out.write(footer); 83 | } catch (IOException e) { 84 | e.printStackTrace(); 85 | } 86 | return out.toByteArray(); 87 | } 88 | 89 | List getParts() throws IOException { 90 | List parts = new ArrayList(mimeParts.size()); 91 | for (MimePart part : mimeParts) { 92 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 93 | part.writeTo(bos); 94 | parts.add(bos.toByteArray()); 95 | } 96 | return parts; 97 | } 98 | 99 | public void addPart(String name, HttpBody body) { 100 | addPart(name, DEFAULT_TRANSFER_ENCODING, body); 101 | } 102 | 103 | public void addPart(String name, String transferEncoding, HttpBody body) { 104 | if (name == null) { 105 | throw new NullPointerException("Part name must not be null."); 106 | } 107 | if (transferEncoding == null) { 108 | throw new NullPointerException("Transfer encoding must not be null."); 109 | } 110 | if (body == null) { 111 | throw new NullPointerException("Part body must not be null."); 112 | } 113 | 114 | MimePart part = new MimePart(name, transferEncoding, body, boundary, mimeParts.isEmpty()); 115 | mimeParts.add(part); 116 | 117 | long size = part.size(); 118 | if (size == -1) { 119 | length = -1; 120 | } else if (length != -1) { 121 | length += size; 122 | } 123 | } 124 | 125 | public int getPartCount() { 126 | return mimeParts.size(); 127 | } 128 | 129 | @Override 130 | public String fileName() { 131 | return null; 132 | } 133 | 134 | @Override 135 | public long length() { 136 | return length; 137 | } 138 | 139 | private static byte[] buildBoundary(String boundary, boolean first, boolean last) { 140 | try { 141 | StringBuilder sb = new StringBuilder(boundary.length() + 8); 142 | 143 | if (!first) { 144 | sb.append("\r\n"); 145 | } 146 | sb.append("--"); 147 | sb.append(boundary); 148 | if (last) { 149 | sb.append("--"); 150 | } 151 | sb.append("\r\n"); 152 | return sb.toString().getBytes("UTF-8"); 153 | } catch (IOException ex) { 154 | throw new RuntimeException("Unable to write multipart boundary", ex); 155 | } 156 | } 157 | 158 | private static byte[] buildHeader(String name, String transferEncoding, HttpBody value) { 159 | try { 160 | StringBuilder headers = new StringBuilder(128); 161 | 162 | headers.append("Content-Disposition: form-data; name=\""); 163 | headers.append(name); 164 | 165 | String fileName = value.fileName(); 166 | if (fileName != null) { 167 | headers.append("\"; filename=\""); 168 | headers.append(fileName); 169 | } 170 | 171 | headers.append("\"\r\nContent-Type: "); 172 | headers.append(value.mimeType()); 173 | 174 | long length = value.length(); 175 | if (length != -1) { 176 | headers.append("\r\nContent-Length: ").append(length); 177 | } 178 | 179 | headers.append("\r\nContent-Transfer-Encoding: "); 180 | headers.append(transferEncoding); 181 | headers.append("\r\n\r\n"); 182 | 183 | return headers.toString().getBytes("UTF-8"); 184 | } catch (IOException ex) { 185 | throw new RuntimeException("Unable to write multipart header", ex); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/client/ApacheClient.java: -------------------------------------------------------------------------------- 1 | package com.santarest.client; 2 | 3 | import com.santarest.http.ByteArrayBody; 4 | import com.santarest.http.Header; 5 | import com.santarest.http.HttpBody; 6 | import com.santarest.http.Request; 7 | import com.santarest.http.Response; 8 | 9 | import org.apache.http.HttpEntity; 10 | import org.apache.http.HttpResponse; 11 | import org.apache.http.StatusLine; 12 | import org.apache.http.client.HttpClient; 13 | import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; 14 | import org.apache.http.client.methods.HttpRequestBase; 15 | import org.apache.http.client.methods.HttpUriRequest; 16 | import org.apache.http.entity.AbstractHttpEntity; 17 | import org.apache.http.impl.client.DefaultHttpClient; 18 | import org.apache.http.message.BasicHeader; 19 | import org.apache.http.params.BasicHttpParams; 20 | import org.apache.http.params.HttpConnectionParams; 21 | import org.apache.http.params.HttpParams; 22 | import org.apache.http.util.EntityUtils; 23 | 24 | import java.io.ByteArrayInputStream; 25 | import java.io.ByteArrayOutputStream; 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.io.OutputStream; 29 | import java.net.URI; 30 | import java.util.ArrayList; 31 | import java.util.List; 32 | 33 | 34 | /** 35 | * A {@link com.santarest.client.HttpClient} which uses an implementation of Apache's {@link HttpClient}. 36 | */ 37 | public class ApacheClient implements com.santarest.client.HttpClient { 38 | private static HttpClient createDefaultClient() { 39 | HttpParams params = new BasicHttpParams(); 40 | HttpConnectionParams.setConnectionTimeout(params, CONNECT_TIMEOUT_MILLIS); 41 | HttpConnectionParams.setSoTimeout(params, READ_TIMEOUT_MILLIS); 42 | return new DefaultHttpClient(params); 43 | } 44 | 45 | private final HttpClient client; 46 | 47 | /** 48 | * Creates an instance backed by {@link DefaultHttpClient}. 49 | */ 50 | public ApacheClient() { 51 | this(createDefaultClient()); 52 | } 53 | 54 | public ApacheClient(HttpClient client) { 55 | this.client = client; 56 | } 57 | 58 | @Override 59 | public Response execute(Request request) throws IOException { 60 | HttpUriRequest apacheRequest = createRequest(request); 61 | HttpResponse apacheResponse = execute(client, apacheRequest); 62 | return parseResponse(request.getUrl(), apacheResponse); 63 | } 64 | 65 | /** 66 | * Execute the specified {@code request} using the provided {@code client}. 67 | */ 68 | protected HttpResponse execute(HttpClient client, HttpUriRequest request) throws IOException { 69 | return client.execute(request); 70 | } 71 | 72 | static HttpUriRequest createRequest(Request request) { 73 | if (request.getBody() != null) { 74 | return new GenericEntityHttpRequest(request); 75 | } 76 | return new GenericHttpRequest(request); 77 | } 78 | 79 | static Response parseResponse(String url, HttpResponse response) throws IOException { 80 | StatusLine statusLine = response.getStatusLine(); 81 | int status = statusLine.getStatusCode(); 82 | String reason = statusLine.getReasonPhrase(); 83 | 84 | List
headers = new ArrayList
(); 85 | String contentType = "application/octet-stream"; 86 | for (org.apache.http.Header header : response.getAllHeaders()) { 87 | String name = header.getName(); 88 | String value = header.getValue(); 89 | if ("Content-Type".equalsIgnoreCase(name)) { 90 | contentType = value; 91 | } 92 | headers.add(new Header(name, value)); 93 | } 94 | 95 | ByteArrayBody body = null; 96 | HttpEntity entity = response.getEntity(); 97 | if (entity != null) { 98 | byte[] bytes = EntityUtils.toByteArray(entity); 99 | body = new ByteArrayBody(contentType, bytes); 100 | } 101 | 102 | return new Response(url, status, reason, headers, body); 103 | } 104 | 105 | private static class GenericHttpRequest extends HttpRequestBase { 106 | private final String method; 107 | 108 | public GenericHttpRequest(Request request) { 109 | method = request.getMethod(); 110 | setURI(URI.create(request.getUrl())); 111 | 112 | // Add all headers. 113 | for (Header header : request.getHeaders()) { 114 | addHeader(new BasicHeader(header.getName(), header.getValue())); 115 | } 116 | } 117 | 118 | @Override 119 | public String getMethod() { 120 | return method; 121 | } 122 | } 123 | 124 | private static class GenericEntityHttpRequest extends HttpEntityEnclosingRequestBase { 125 | private final String method; 126 | 127 | GenericEntityHttpRequest(Request request) { 128 | super(); 129 | method = request.getMethod(); 130 | setURI(URI.create(request.getUrl())); 131 | 132 | // Add all headers. 133 | for (Header header : request.getHeaders()) { 134 | addHeader(new BasicHeader(header.getName(), header.getValue())); 135 | } 136 | 137 | // Add the content body. 138 | setEntity(new TypedOutputEntity(request.getBody())); 139 | } 140 | 141 | @Override 142 | public String getMethod() { 143 | return method; 144 | } 145 | } 146 | 147 | /** 148 | * Container class for passing an entire {@link HttpBody} as an {@link HttpEntity}. 149 | */ 150 | static class TypedOutputEntity extends AbstractHttpEntity { 151 | final HttpBody requestBody; 152 | 153 | TypedOutputEntity(HttpBody requestBody) { 154 | this.requestBody = requestBody; 155 | setContentType(requestBody.mimeType()); 156 | } 157 | 158 | @Override 159 | public boolean isRepeatable() { 160 | return true; 161 | } 162 | 163 | @Override 164 | public long getContentLength() { 165 | return requestBody.length(); 166 | } 167 | 168 | @Override 169 | public InputStream getContent() throws IOException { 170 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 171 | requestBody.writeTo(out); 172 | return new ByteArrayInputStream(out.toByteArray()); 173 | } 174 | 175 | @Override 176 | public void writeTo(OutputStream out) throws IOException { 177 | requestBody.writeTo(out); 178 | } 179 | 180 | @Override 181 | public boolean isStreaming() { 182 | return false; 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/RequestBuilder.java: -------------------------------------------------------------------------------- 1 | package com.santarest; 2 | 3 | import com.santarest.annotations.RestAction; 4 | import com.santarest.converter.Converter; 5 | import com.santarest.http.FormUrlEncodedRequestBody; 6 | import com.santarest.http.Header; 7 | import com.santarest.http.HttpBody; 8 | import com.santarest.http.MultipartRequestBody; 9 | import com.santarest.http.Request; 10 | 11 | import java.io.IOException; 12 | import java.io.OutputStream; 13 | import java.io.UnsupportedEncodingException; 14 | import java.lang.reflect.Array; 15 | import java.net.URLEncoder; 16 | import java.util.ArrayList; 17 | import java.util.Collections; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | public final class RequestBuilder { 22 | 23 | private final Converter converter; 24 | private final String apiUrl; 25 | 26 | private FormUrlEncodedRequestBody formBody; 27 | private MultipartRequestBody multipartBody; 28 | private HttpBody body; 29 | 30 | private String path = ""; 31 | private StringBuilder queryParams; 32 | private List
headers; 33 | private String contentTypeHeader; 34 | private RestAction.Method requestMethod = RestAction.Method.GET; 35 | 36 | RequestBuilder(String apiUrl, Converter converter) { 37 | this.apiUrl = apiUrl; 38 | this.converter = converter; 39 | this.multipartBody = null; 40 | this.formBody = null; 41 | } 42 | 43 | public void setRequestType(RestAction.Type type) { 44 | switch (type) { 45 | case FORM_URL_ENCODED: 46 | formBody = new FormUrlEncodedRequestBody(); 47 | multipartBody = null; 48 | body = formBody; 49 | break; 50 | case MULTIPART: 51 | formBody = null; 52 | multipartBody = new MultipartRequestBody(); 53 | body = multipartBody; 54 | break; 55 | case SIMPLE: 56 | formBody = null; 57 | multipartBody = null; 58 | break; 59 | default: 60 | throw new IllegalArgumentException("Unknown request type: " + type); 61 | } 62 | } 63 | 64 | public void addHeader(String name, String value) { 65 | if (name == null) { 66 | throw new IllegalArgumentException("Header name must not be null."); 67 | } 68 | if ("Content-Type".equalsIgnoreCase(name)) { 69 | contentTypeHeader = value; 70 | return; 71 | } 72 | 73 | List
headers = this.headers; 74 | if (headers == null) { 75 | this.headers = headers = new ArrayList
(2); 76 | } 77 | headers.add(new Header(name, value)); 78 | } 79 | 80 | public void setPath(String path) { 81 | this.path = path; 82 | } 83 | 84 | public void addPathParam(String name, String value) { 85 | addPathParam(name, value, true); 86 | } 87 | 88 | public void addPathParam(String name, String value, boolean urlEncodeValue) { 89 | if (name == null) { 90 | throw new IllegalArgumentException("Path replacement name must not be null."); 91 | } 92 | if (value == null) { 93 | throw new IllegalArgumentException( 94 | "Path replacement \"" + name + "\" value must not be null."); 95 | } 96 | try { 97 | if (urlEncodeValue) { 98 | String encodedValue = URLEncoder.encode(String.valueOf(value), "UTF-8"); 99 | // URLEncoder encodes for use as a query parameter. Path encoding uses %20 to 100 | // encode spaces rather than +. Query encoding difference specified in HTML spec. 101 | // Any remaining plus signs represent spaces as already URLEncoded. 102 | encodedValue = encodedValue.replace("+", "%20"); 103 | path = path.replace("{" + name + "}", encodedValue); 104 | } else { 105 | path = path.replace("{" + name + "}", String.valueOf(value)); 106 | } 107 | } catch (UnsupportedEncodingException e) { 108 | throw new RuntimeException( 109 | "Unable to convert path parameter \"" + name + "\" value to UTF-8:" + value, e); 110 | } 111 | } 112 | 113 | public void addQueryParam(String name, Object value, boolean encodeName, boolean encodeValue) { 114 | if (value instanceof Iterable) { 115 | for (Object iterableValue : (Iterable) value) { 116 | if (iterableValue != null) { // Skip null values 117 | addQueryParam(name, iterableValue.toString(), encodeName, encodeValue); 118 | } 119 | } 120 | } else if (value.getClass().isArray()) { 121 | for (int x = 0, arrayLength = Array.getLength(value); x < arrayLength; x++) { 122 | Object arrayValue = Array.get(value, x); 123 | if (arrayValue != null) { // Skip null values 124 | addQueryParam(name, arrayValue.toString(), encodeName, encodeValue); 125 | } 126 | } 127 | } else { 128 | addQueryParam(name, value.toString(), encodeName, encodeValue); 129 | } 130 | } 131 | 132 | private void addQueryParam(String name, String value, boolean encodeName, boolean encodeValue) { 133 | if (name == null) { 134 | throw new IllegalArgumentException("Query param name must not be null."); 135 | } 136 | if (value == null) { 137 | throw new IllegalArgumentException("Query param \"" + name + "\" value must not be null."); 138 | } 139 | try { 140 | StringBuilder queryParams = this.queryParams; 141 | if (queryParams == null) { 142 | this.queryParams = queryParams = new StringBuilder(); 143 | } 144 | 145 | queryParams.append(queryParams.length() > 0 ? '&' : '?'); 146 | 147 | if (encodeName) { 148 | name = URLEncoder.encode(name, "UTF-8"); 149 | } 150 | if (encodeValue) { 151 | value = URLEncoder.encode(value, "UTF-8"); 152 | } 153 | queryParams.append(name).append('=').append(value); 154 | } catch (UnsupportedEncodingException e) { 155 | throw new RuntimeException( 156 | "Unable to convert query parameter \"" + name + "\" value to UTF-8: " + value, e); 157 | } 158 | } 159 | 160 | public void addQueryParamMap(Map map, boolean encodeNames, boolean encodeValues) { 161 | for (Map.Entry entry : map.entrySet()) { 162 | Object entryKey = entry.getKey(); 163 | Object entryValue = entry.getValue(); 164 | if (entryValue != null) { // Skip null values. 165 | addQueryParam(entryKey.toString(), entryValue.toString(), encodeNames, encodeValues); 166 | } 167 | } 168 | } 169 | 170 | public void addField(String name, Object value) { 171 | if (value != null) { // Skip null values. 172 | if (value instanceof Iterable) { 173 | for (Object iterableValue : (Iterable) value) { 174 | if (iterableValue != null) { // Skip null values. 175 | formBody.addField(name, iterableValue.toString()); 176 | } 177 | } 178 | } else if (value.getClass().isArray()) { 179 | for (int x = 0, arrayLength = Array.getLength(value); x < arrayLength; x++) { 180 | Object arrayValue = Array.get(value, x); 181 | if (arrayValue != null) { // Skip null values. 182 | formBody.addField(name, arrayValue.toString()); 183 | } 184 | } 185 | } else { 186 | formBody.addField(name, value.toString()); 187 | } 188 | } 189 | } 190 | 191 | public void addPart(String name, HttpBody body, String transferEncoding) { 192 | multipartBody.addPart(name, transferEncoding, body); 193 | } 194 | 195 | public void setBody(Object value) { 196 | if (multipartBody != null || formBody != null) { 197 | throw new IllegalArgumentException("Request is not simple type"); 198 | } 199 | if (value == null) { 200 | throw new IllegalArgumentException("Body parameter value must not be null."); 201 | } 202 | if (value instanceof HttpBody) { 203 | body = (HttpBody) value; 204 | } else { 205 | body = converter.toBody(value); 206 | } 207 | } 208 | 209 | public void setMethod(RestAction.Method method) { 210 | this.requestMethod = method; 211 | } 212 | 213 | public Request build() { 214 | if (multipartBody != null && multipartBody.getPartCount() == 0) { 215 | throw new IllegalStateException("Multipart requests must contain at least one part."); 216 | } 217 | String apiUrl = this.apiUrl; 218 | StringBuilder url = new StringBuilder(apiUrl); 219 | if (apiUrl.endsWith("/")) { 220 | // We require relative paths to start with '/'. Prevent a double-slash. 221 | url.deleteCharAt(url.length() - 1); 222 | } 223 | url.append(path); 224 | StringBuilder queryParams = this.queryParams; 225 | if (queryParams != null) { 226 | url.append(queryParams); 227 | } 228 | HttpBody body = this.body; 229 | List
headers = this.headers; 230 | if (contentTypeHeader != null) { 231 | if (body != null) { 232 | body = new MimeOverridingTypedOutput(body, contentTypeHeader); 233 | } else { 234 | Header header = new Header("Content-Type", contentTypeHeader); 235 | if (headers == null) { 236 | headers = Collections.singletonList(header); 237 | } else { 238 | headers.add(header); 239 | } 240 | } 241 | } 242 | return new Request(requestMethod.name(), url.toString(), headers, body); 243 | } 244 | 245 | private static class MimeOverridingTypedOutput extends HttpBody { 246 | private final HttpBody delegate; 247 | private final String mimeType; 248 | 249 | MimeOverridingTypedOutput(HttpBody delegate, String mimeType) { 250 | super(mimeType); 251 | this.delegate = delegate; 252 | this.mimeType = mimeType; 253 | } 254 | 255 | @Override 256 | public byte[] getContent() throws IOException { 257 | return delegate.getContent(); 258 | } 259 | 260 | @Override 261 | public String fileName() { 262 | return delegate.fileName(); 263 | } 264 | 265 | @Override 266 | public String mimeType() { 267 | return mimeType; 268 | } 269 | 270 | @Override 271 | public long length() { 272 | return delegate.length(); 273 | } 274 | 275 | @Override 276 | public void writeTo(OutputStream out) throws IOException { 277 | delegate.writeTo(out); 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /santarest-compiler/src/main/java/com/santarest/HelpersGenerator.java: -------------------------------------------------------------------------------- 1 | package com.santarest; 2 | 3 | 4 | import com.google.gson.reflect.TypeToken; 5 | import com.santarest.annotations.Body; 6 | import com.santarest.annotations.Error; 7 | import com.santarest.annotations.Field; 8 | import com.santarest.annotations.Part; 9 | import com.santarest.annotations.Path; 10 | import com.santarest.annotations.Query; 11 | import com.santarest.annotations.RequestHeader; 12 | import com.santarest.annotations.ResponseHeader; 13 | import com.santarest.annotations.RestAction; 14 | import com.santarest.annotations.Status; 15 | import com.santarest.converter.Converter; 16 | import com.santarest.http.ByteArrayBody; 17 | import com.santarest.http.FileBody; 18 | import com.santarest.http.Header; 19 | import com.santarest.http.HttpBody; 20 | import com.santarest.http.Response; 21 | import com.squareup.javapoet.ClassName; 22 | import com.squareup.javapoet.MethodSpec; 23 | import com.squareup.javapoet.ParameterizedTypeName; 24 | import com.squareup.javapoet.TypeName; 25 | import com.squareup.javapoet.TypeSpec; 26 | 27 | import org.apache.commons.lang.StringUtils; 28 | 29 | import java.io.File; 30 | import java.util.ArrayList; 31 | import java.util.HashMap; 32 | import java.util.List; 33 | 34 | import javax.annotation.processing.Filer; 35 | import javax.lang.model.element.Element; 36 | import javax.lang.model.element.Modifier; 37 | 38 | import static com.santarest.TypeUtils.equalTypes; 39 | 40 | public class HelpersGenerator extends Generator { 41 | static final String HELPER_SUFFIX = "Helper"; 42 | private static final String BASE_HEADERS_MAP = "headers"; 43 | 44 | public HelpersGenerator(Filer filer) { 45 | super(filer); 46 | } 47 | 48 | @Override 49 | public void generate(ArrayList actionClasses) { 50 | for (RestActionClass restActionClass : actionClasses) { 51 | generate(restActionClass); 52 | } 53 | } 54 | 55 | private void generate(RestActionClass actionClass) { 56 | TypeSpec.Builder classBuilder = TypeSpec.classBuilder(actionClass.getHelperName()) 57 | .addModifiers(Modifier.PUBLIC) 58 | .addJavadoc("SantaRest compile time, autogenerated class, which fills actions") 59 | .addSuperinterface(ParameterizedTypeName.get(ClassName.get(SantaRest.ActionHelper.class), actionClass.getTypeName())); 60 | 61 | classBuilder.addMethod(createFillRequestMethod(actionClass)); 62 | classBuilder.addMethod(createOnResponseMethod(actionClass)); 63 | classBuilder.addMethod(createOnErrorMethod(actionClass)); 64 | saveClass(actionClass.getPackageName(), classBuilder.build()); 65 | } 66 | 67 | private MethodSpec createOnErrorMethod(RestActionClass actionClass) { 68 | MethodSpec.Builder builder = MethodSpec.methodBuilder("onError") 69 | .addModifiers(Modifier.PUBLIC) 70 | .addAnnotation(Override.class) 71 | .returns(actionClass.getTypeName()) 72 | .addParameter(actionClass.getTypeName(), "action") 73 | .addParameter(Throwable.class, "error"); 74 | for (Element element : actionClass.getAnnotatedElements(Error.class)) { 75 | String fieldAddress = getFieldAddress(actionClass, element); 76 | if (TypeUtils.containsType(element, Throwable.class)) { 77 | builder.addStatement(fieldAddress + " = error", element); 78 | } else if (TypeUtils.containsType(element, Exception.class)) { 79 | builder.addStatement(fieldAddress + " = ($T) error", element, Exception.class); 80 | } 81 | } 82 | builder.addStatement("return action"); 83 | return builder.build(); 84 | } 85 | 86 | private MethodSpec createFillRequestMethod(RestActionClass actionClass) { 87 | MethodSpec.Builder builder = MethodSpec.methodBuilder("fillRequest") 88 | .addModifiers(Modifier.PUBLIC) 89 | .addAnnotation(Override.class) 90 | .returns(RequestBuilder.class) 91 | .addParameter(RequestBuilder.class, "requestBuilder") 92 | .addParameter(TypeName.get(actionClass.getTypeElement().asType()), "action") 93 | .addStatement("requestBuilder.setMethod($T.$L)", RestAction.Method.class, actionClass.getMethod()) 94 | .addStatement("requestBuilder.setRequestType($T.$L)", RestAction.Type.class, actionClass.getRequestType()) 95 | .addStatement("requestBuilder.setPath($S)", actionClass.getPath()); 96 | addPathParams(actionClass, builder); 97 | addParts(actionClass, builder); 98 | addRequestHeaders(actionClass, builder); 99 | addRequestFields(actionClass, builder); 100 | addRequestQueries(actionClass, builder); 101 | addRequestBody(actionClass, builder); 102 | builder.addStatement("return requestBuilder"); 103 | return builder.build(); 104 | } 105 | 106 | //TODO: replace logic like in retrofit 107 | private void addRequestFields(RestActionClass actionClass, MethodSpec.Builder builder) { 108 | for (Element element : actionClass.getAnnotatedElements(Field.class)) { 109 | Field annotation = element.getAnnotation(Field.class); 110 | builder.beginControlFlow("if (action.$L != null)", element); 111 | builder.addStatement("requestBuilder.addField($S, action.$L.toString())", annotation.value(), element); 112 | builder.endControlFlow(); 113 | } 114 | } 115 | 116 | private void addRequestQueries(RestActionClass actionClass, MethodSpec.Builder builder) { 117 | for (Element element : actionClass.getAnnotatedElements(Query.class)) { 118 | Query annotation = element.getAnnotation(Query.class); 119 | if (TypeUtils.isPrimitive(element)) { 120 | builder.addStatement("requestBuilder.addQueryParam($S, $T.valueOf(action.$L), $L, $L)", annotation.value(), String.class, element, annotation.encodeName(), annotation.encodeValue()); 121 | } else { 122 | builder.beginControlFlow("if (action.$L != null)", element); 123 | builder.addStatement("requestBuilder.addQueryParam($S, action.$L.toString(), $L, $L)", annotation.value(), element, annotation.encodeName(), annotation.encodeValue()); 124 | builder.endControlFlow(); 125 | } 126 | } 127 | } 128 | 129 | private void addRequestBody(RestActionClass actionClass, MethodSpec.Builder builder) { 130 | for (Element element : actionClass.getAnnotatedElements(Body.class)) { 131 | builder.addStatement("requestBuilder.setBody(action.$L)", element); 132 | break; 133 | } 134 | } 135 | 136 | private void addRequestHeaders(RestActionClass actionClass, MethodSpec.Builder builder) { 137 | for (Element element : actionClass.getAnnotatedElements(RequestHeader.class)) { 138 | RequestHeader annotation = element.getAnnotation(RequestHeader.class); 139 | builder.beginControlFlow("if (action.$L != null)", element); 140 | builder.addStatement("requestBuilder.addHeader($S, action.$L.toString())", annotation.value(), element); 141 | builder.endControlFlow(); 142 | } 143 | } 144 | 145 | private void addPathParams(RestActionClass actionClass, MethodSpec.Builder builder) { 146 | for (Element element : actionClass.getAnnotatedElements(Path.class)) { 147 | Path param = element.getAnnotation(Path.class); 148 | String path = param.value(); 149 | String name = element.getSimpleName().toString(); 150 | if (StringUtils.isEmpty(path)) { 151 | path = name; 152 | } 153 | boolean encode = param.encode(); 154 | builder.beginControlFlow("if (action.$L != null)", name); 155 | builder.addStatement("requestBuilder.addPathParam($S, action.$L.toString(), $L)", path, name, encode); 156 | builder.endControlFlow(); 157 | } 158 | } 159 | 160 | private void addParts(RestActionClass actionClass, MethodSpec.Builder builder) { 161 | for (Element element : actionClass.getAnnotatedElements(Part.class)) { 162 | Part part = element.getAnnotation(Part.class); 163 | String partName = part.value(); 164 | String name = element.getSimpleName().toString(); 165 | if (StringUtils.isEmpty(partName)) { 166 | partName = name; 167 | } 168 | String encode = part.encoding(); 169 | String httpBodyName = "httpBody"; 170 | builder.beginControlFlow("if (action.$L != null)", name); 171 | if (TypeUtils.equalTypes(element, File.class)) { 172 | builder.addStatement("$T $L = new $T($S, action.$L)", HttpBody.class,httpBodyName, FileBody.class, encode, name); 173 | } else if (TypeUtils.equalTypes(element, byte[].class)) { 174 | builder.addStatement("$T $L = new $T($S, action.$L)", HttpBody.class,httpBodyName, ByteArrayBody.class, encode, name); 175 | } else if (TypeUtils.equalTypes(element, String.class)) { 176 | builder.addStatement("$T $L = new $T($S, action.$L.getBytes())",HttpBody.class, httpBodyName, ByteArrayBody.class, encode, name); 177 | } else { 178 | builder.addStatement("$T $L = action.$L", HttpBody.class,httpBodyName, name); 179 | } 180 | builder.addStatement("requestBuilder.addPart($S, $L, $S)", partName, httpBodyName, encode); 181 | builder.endControlFlow(); 182 | } 183 | } 184 | 185 | private MethodSpec createOnResponseMethod(RestActionClass actionClass) { 186 | MethodSpec.Builder builder = MethodSpec.methodBuilder("onResponse") 187 | .addModifiers(Modifier.PUBLIC) 188 | .addAnnotation(Override.class) 189 | .returns(ClassName.get(actionClass.getTypeElement().asType())) 190 | .addParameter(actionClass.getTypeName(), "action") 191 | .addParameter(Response.class, "response") 192 | .addParameter(Converter.class, "converter"); 193 | 194 | addStatusField(actionClass, builder); 195 | addResponses(actionClass, builder); 196 | addBasicHeadersMap(actionClass, builder); 197 | addResponseHeaders(actionClass, builder); 198 | builder.addStatement("return action"); 199 | return builder.build(); 200 | } 201 | 202 | private void addResponses(RestActionClass actionClass, MethodSpec.Builder builder) { 203 | List responseElements = actionClass.getAnnotatedElements(com.santarest.annotations.Response.class); 204 | 205 | for (Element element : responseElements) { 206 | int status = element.getAnnotation(com.santarest.annotations.Response.class).value(); 207 | if (status > 0) { 208 | builder.beginControlFlow("if(response.getStatus() == $L)", status); 209 | addResponseStatements(actionClass, builder, element); 210 | builder.endControlFlow(); 211 | } else { 212 | addResponseStatements(actionClass, builder, element); 213 | } 214 | } 215 | } 216 | 217 | private void addResponseStatements(RestActionClass actionClass, MethodSpec.Builder builder, Element element) { 218 | String fieldAddress = getFieldAddress(actionClass, element); 219 | if (equalTypes(element, HttpBody.class)) { 220 | builder.addStatement(fieldAddress + " = response.getBody()", element); 221 | } else if (equalTypes(element, String.class)) { 222 | builder.addStatement(fieldAddress + " = response.getBody().toString()", element); 223 | } else { 224 | builder.addStatement(fieldAddress + " = ($T) converter.fromBody(response.getBody(), new $T<$T>(){}.getType())", element, element.asType(), TypeToken.class, element.asType()); 225 | } 226 | } 227 | 228 | private void addBasicHeadersMap(RestActionClass actionClass, MethodSpec.Builder builder) { 229 | if (actionClass.getAnnotatedElements(ResponseHeader.class).isEmpty()) { 230 | return; 231 | } 232 | builder.addStatement("$T<$T, $T> $L = new $T<$T, $T>()", HashMap.class, String.class, String.class, BASE_HEADERS_MAP, HashMap.class, String.class, String.class); 233 | builder.beginControlFlow("for ($T header : response.getHeaders())", Header.class); 234 | builder.addStatement("$L.put(header.getName(), header.getValue())", BASE_HEADERS_MAP); 235 | builder.endControlFlow(); 236 | } 237 | 238 | private void addResponseHeaders(RestActionClass actionClass, MethodSpec.Builder builder) { 239 | for (Element element : actionClass.getAnnotatedElements(ResponseHeader.class)) { 240 | ResponseHeader annotation = element.getAnnotation(ResponseHeader.class); 241 | String fieldAddress = getFieldAddress(actionClass, element); 242 | builder.addStatement(fieldAddress + " = $L.get($S)", element.toString(), BASE_HEADERS_MAP, annotation.value()); 243 | } 244 | } 245 | 246 | private void addStatusField(RestActionClass actionClass, MethodSpec.Builder builder) { 247 | for (Element element : actionClass.getAnnotatedElements(Status.class)) { 248 | String fieldAddress = getFieldAddress(actionClass, element); 249 | if (TypeUtils.containsType(element, Boolean.class, boolean.class)) { 250 | builder.addStatement(fieldAddress + " = response.isSuccessful()", element); 251 | } else if (TypeUtils.containsType(element, Integer.class, int.class, long.class)) { 252 | builder.addStatement(fieldAddress + " = ($T) response.getStatus()", element, element.asType()); 253 | } else if (equalTypes(element, String.class)) { 254 | builder.addStatement(fieldAddress + " = Integer.toString(response.getStatus())", element); 255 | } else if (TypeUtils.containsType(element, Long.class)) { 256 | builder.addStatement(fieldAddress + " = (long) response.getStatus()", element); 257 | } 258 | } 259 | } 260 | 261 | private static String getFieldAddress(RestActionClass actionClass, Element element) { 262 | String address; 263 | if (actionClass.getTypeElement().equals(element.getEnclosingElement())) { 264 | address = "action.$L"; 265 | } else { 266 | address = String.format("((%s)action).$L", element.getEnclosingElement()); 267 | } 268 | return address; 269 | } 270 | 271 | } -------------------------------------------------------------------------------- /santarest/src/main/java/com/santarest/SantaRest.java: -------------------------------------------------------------------------------- 1 | package com.santarest; 2 | 3 | import com.santarest.annotations.RestAction; 4 | import com.santarest.callback.Callback; 5 | import com.santarest.client.HttpClient; 6 | import com.santarest.converter.Converter; 7 | import com.santarest.http.Request; 8 | import com.santarest.http.Response; 9 | import com.santarest.utils.Logger; 10 | 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.concurrent.Executor; 16 | 17 | import rx.Observable; 18 | import rx.subjects.PublishSubject; 19 | import rx.subjects.Subject; 20 | 21 | public class SantaRest { 22 | 23 | final static String HELPERS_FACTORY_CLASS_SIMPLE_NAME = "ActionHelperFactoryImpl"; 24 | private final static String HELPERS_FACTORY_CLASS_NAME = SantaRest.class.getPackage().getName() + "." + HELPERS_FACTORY_CLASS_SIMPLE_NAME; 25 | 26 | private final String serverUrl; 27 | private final HttpClient client; 28 | private final Executor executor; 29 | private final Executor callbackExecutor; 30 | private final List requestInterceptors; 31 | private final List responseInterceptors; 32 | private final Converter converter; 33 | private final ActionPoster actionPoster; 34 | private final Subject signal; 35 | private final Logger logger; 36 | 37 | private final Map actionHelperCache = new HashMap(); 38 | private ActionHelperFactory actionHelperFactory; 39 | 40 | private SantaRest(Builder builder) { 41 | this.serverUrl = builder.serverUrl; 42 | this.client = builder.client; 43 | this.executor = builder.executor; 44 | this.callbackExecutor = builder.callbackExecutor; 45 | this.requestInterceptors = builder.requestInterceptors; 46 | this.responseInterceptors = builder.responseInterceptors; 47 | this.converter = builder.converter; 48 | this.actionPoster = builder.actionPoster; 49 | this.signal = PublishSubject.create(); 50 | this.logger = Defaults.getLogger(); 51 | loadActionHelperFactory(); 52 | } 53 | 54 | private void loadActionHelperFactory() { 55 | try { 56 | Class clazz 57 | = (Class) Class.forName(HELPERS_FACTORY_CLASS_NAME); 58 | actionHelperFactory = clazz.newInstance(); 59 | } catch (Exception e) { 60 | //do nothing. actionHelperFactory will be checked on run action 61 | } 62 | } 63 | 64 | /** 65 | * Request will be performed in executing thread and return action with filled response fields. 66 | * 67 | * @param action any object annotated with 68 | * @see com.santarest.annotations.RestAction 69 | */ 70 | public A runAction(A action) { 71 | ActionHelper helper = getActionHelper(action.getClass()); 72 | if (helper == null) { 73 | throw new SantaRestException("Action object should be annotated by " + RestAction.class.getName() + " or check dependence of santarest-compiler"); 74 | } 75 | RequestBuilder builder = new RequestBuilder(serverUrl, converter); 76 | builder = helper.fillRequest(builder, action); 77 | for (RequestInterceptor requestInterceptor : requestInterceptors) { 78 | requestInterceptor.intercept(builder); 79 | } 80 | Request request = builder.build(); 81 | try { 82 | String nameActionForlog = action.getClass().getSimpleName(); 83 | logger.log("Start executing request %s", nameActionForlog); 84 | Response response = client.execute(request); 85 | logger.log("Received response of %s", nameActionForlog); 86 | action = helper.onResponse(action, response, converter); 87 | for (ResponseListener listener : responseInterceptors) { 88 | listener.onResponseReceived(action, request, response); 89 | } 90 | logger.log("Filled response of %s using helper %s", nameActionForlog, helper.getClass().getSimpleName()); 91 | } catch (Exception error) { 92 | logger.error("Failed action %s executing", action.getClass().getSimpleName()); 93 | for (StackTraceElement element : error.getStackTrace()) { 94 | logger.error("%s", element.toString()); 95 | } 96 | action = helper.onError(action, error); 97 | } 98 | return action; 99 | } 100 | 101 | public Observable createObservable(A action) { 102 | return Observable 103 | .create(new RXOnSubscribe(action) { 104 | @Override 105 | protected void doAction(A action) { 106 | runAction(action); 107 | } 108 | }); 109 | } 110 | 111 | /** 112 | * Request will be performed in working thread 113 | * 114 | * @param action any object annotated with 115 | * @see com.santarest.annotations.RestAction 116 | */ 117 | public void sendAction(A action) { 118 | sendAction(action, null); 119 | } 120 | 121 | /** 122 | * Request will be performed in working thread 123 | * 124 | * @param action any object annotated with 125 | * @see com.santarest.annotations.RestAction 126 | */ 127 | public void sendAction(final A action, Callback callback) { 128 | CallbackWrapper callbackWrapper = new CallbackWrapper(actionPoster, signal, callback); 129 | executor.execute(new CallbackRunnable(action, callbackWrapper, callbackExecutor) { 130 | @Override 131 | protected void doAction(A action) { 132 | runAction(action); 133 | } 134 | }); 135 | } 136 | 137 | /** 138 | * Get ActionPoster for subscribe actions after server response. 139 | * @return actionPoster which was set 140 | */ 141 | public ActionPoster getActionPoster() { 142 | return actionPoster; 143 | } 144 | 145 | 146 | /** 147 | * Subscribe to receive actions using Observable 148 | * @return Observable 149 | */ 150 | public Observable observeActions() { 151 | return signal.asObservable(); 152 | } 153 | 154 | private ActionHelper getActionHelper(Class actionClass) { 155 | ActionHelper helper = actionHelperCache.get(actionClass); 156 | if (helper == null && actionHelperFactory != null) { 157 | synchronized (this) { 158 | helper = actionHelperFactory.make(actionClass); 159 | actionHelperCache.put(actionClass, helper); 160 | } 161 | } 162 | return helper; 163 | } 164 | 165 | public interface ActionHelper { 166 | RequestBuilder fillRequest(RequestBuilder requestBuilder, T action); 167 | 168 | T onResponse(T action, Response response, Converter converter); 169 | 170 | T onError(T action, Throwable error); 171 | } 172 | 173 | interface ActionHelperFactory { 174 | ActionHelper make(Class actionClass); 175 | } 176 | 177 | /** 178 | * Intercept every request before it is executed. 179 | */ 180 | public interface RequestInterceptor { 181 | /** 182 | * Called for every request. You can add your data to builder before create request 183 | * 184 | * @param request 185 | */ 186 | void intercept(RequestBuilder request); 187 | } 188 | 189 | /** 190 | * Intercept every response. 191 | */ 192 | public interface ResponseListener { 193 | /** 194 | * Called for every get response. You can get data from response after invoke it 195 | */ 196 | void onResponseReceived(A action, Request request, Response response); 197 | 198 | } 199 | 200 | private static class CallbackWrapper implements Callback { 201 | 202 | private final ActionPoster actionPoster; 203 | private final Subject signal; 204 | private final Callback callback; 205 | 206 | private CallbackWrapper(ActionPoster actionPoster, Subject signal, Callback callback) { 207 | this.signal = signal; 208 | this.actionPoster = actionPoster; 209 | this.callback = callback; 210 | } 211 | 212 | @Override 213 | public void onSuccess(A action) { 214 | if (actionPoster != null) { 215 | actionPoster.post(action); 216 | } 217 | if (signal != null) { 218 | signal.onNext(action); 219 | } 220 | if (callback != null) { 221 | callback.onSuccess(action); 222 | } 223 | } 224 | 225 | @Override 226 | public void onFail(A action, Exception error) { 227 | if (actionPoster != null) { 228 | actionPoster.post(action); 229 | } 230 | if (signal != null) { 231 | signal.onNext(action); 232 | } 233 | if (callback != null) { 234 | callback.onFail(action, error); 235 | } 236 | } 237 | } 238 | 239 | private static abstract class CallbackRunnable implements Runnable { 240 | private final Callback callback; 241 | private final Executor callbackExecutor; 242 | private final A action; 243 | 244 | private CallbackRunnable(A action, Callback callback, Executor callbackExecutor) { 245 | this.action = action; 246 | this.callback = callback; 247 | this.callbackExecutor = callbackExecutor; 248 | } 249 | 250 | @Override 251 | public final void run() { 252 | try { 253 | doAction(action); 254 | callbackExecutor.execute(new Runnable() { 255 | @Override 256 | public void run() { 257 | callback.onSuccess(action); 258 | } 259 | }); 260 | } catch (SantaRestException e) { 261 | throw e; 262 | } catch (final Exception e) { 263 | callbackExecutor.execute(new Runnable() { 264 | @Override 265 | public void run() { 266 | callback.onFail(action, e); 267 | } 268 | }); 269 | } 270 | } 271 | 272 | protected abstract void doAction(A action); 273 | } 274 | 275 | 276 | public static class Builder { 277 | private String serverUrl; 278 | private HttpClient client; 279 | private Executor executor; 280 | private Executor callbackExecutor; 281 | private List requestInterceptors = new ArrayList(); 282 | private List responseInterceptors = new ArrayList(); 283 | private Converter converter; 284 | private ActionPoster actionPoster; 285 | 286 | /** 287 | * API URL. 288 | */ 289 | public Builder setServerUrl(String serverUrl) { 290 | if (serverUrl == null || serverUrl.trim().length() == 0) { 291 | throw new IllegalArgumentException("Endpoint may not be blank."); 292 | } 293 | this.serverUrl = serverUrl; 294 | return this; 295 | } 296 | 297 | /** 298 | * The HTTP client used for requests. 299 | */ 300 | public Builder setClient(HttpClient client) { 301 | if (client == null) { 302 | throw new IllegalArgumentException("Client provider may not be null."); 303 | } 304 | this.client = client; 305 | return this; 306 | } 307 | 308 | /** 309 | * Executors used for asynchronous HTTP client downloads and callbacks. 310 | * 311 | * @param httpExecutor Executor on which HTTP client calls will be made. 312 | */ 313 | public Builder setExecutor(Executor httpExecutor) { 314 | if (httpExecutor == null) { 315 | throw new IllegalArgumentException("HTTP executor may not be null."); 316 | } 317 | this.executor = httpExecutor; 318 | return this; 319 | } 320 | 321 | public Builder setCallbackExecutor(Executor callbackExecutor) { 322 | if (callbackExecutor == null) { 323 | throw new IllegalArgumentException("HTTP executor may not be null."); 324 | } 325 | this.callbackExecutor = callbackExecutor; 326 | return this; 327 | } 328 | 329 | public Builder addRequestInterceptor(RequestInterceptor requestInterceptor) { 330 | if (requestInterceptor == null) { 331 | throw new IllegalArgumentException("Request interceptor may not be null."); 332 | } 333 | this.requestInterceptors.add(requestInterceptor); 334 | return this; 335 | } 336 | 337 | public Builder addResponseInterceptor(ResponseListener responseListener) { 338 | if (responseListener == null) { 339 | throw new IllegalArgumentException("Request interceptor may not be null."); 340 | } 341 | this.responseInterceptors.add(responseListener); 342 | return this; 343 | } 344 | 345 | /** 346 | * The converter used for serialization and deserialization of objects. 347 | * 348 | * @see com.santarest.converter.GsonConverter 349 | */ 350 | public Builder setConverter(Converter converter) { 351 | if (converter == null) { 352 | throw new IllegalArgumentException("Converter may not be null."); 353 | } 354 | this.converter = converter; 355 | return this; 356 | } 357 | 358 | /** 359 | * For example action poster wrapper of buses 360 | * 361 | * @see de.greenrobot.event.EventBus 362 | * @see com.squareup.otto.Bus 363 | */ 364 | public Builder setActionPoster(ActionPoster actionPoster) { 365 | if (actionPoster == null) { 366 | throw new IllegalArgumentException("ActionPoster may not be null."); 367 | } 368 | this.actionPoster = actionPoster; 369 | return this; 370 | } 371 | 372 | /** 373 | * Create the {@link SantaRest} instance. 374 | */ 375 | public SantaRest build() { 376 | if (serverUrl == null) { 377 | throw new IllegalArgumentException("Server url may not be null."); 378 | } 379 | fillDefaults(); 380 | return new SantaRest(this); 381 | } 382 | 383 | private void fillDefaults() { 384 | if (converter == null) { 385 | converter = Defaults.getConverter(); 386 | } 387 | if (client == null) { 388 | client = Defaults.getClient(); 389 | } 390 | if (executor == null) { 391 | executor = Defaults.getDefaultHttpExecutor(); 392 | } 393 | if (callbackExecutor == null) { 394 | callbackExecutor = Defaults.getDefaultCallbackExecutor(); 395 | } 396 | if (actionPoster == null) { 397 | actionPoster = Defaults.getDefualtActionPoster(); 398 | } 399 | } 400 | } 401 | } 402 | --------------------------------------------------------------------------------