public class DNKErrorHandler implements ErrorHandler {
    @Override
    public Throwable handleError(RetrofitError cause) {

        if(cause.getResponse() == null) //No connection¿? //todo improve this
            return createException(cause);

        switch (cause.getResponse().getStatus()) {
            case DNKErrorCodes.AUTHENTICATION_REQUIRED:
                DNKUnauthorizedResponse errorResponse = (DNKUnauthorizedResponse)cause.getBodyAs(DNKUnauthorizedResponse.class);
                DroidNubeKit.showAuthDialog(errorResponse.getRedirectUrl());

                return new DNKAuthenticationRequiredException(DNKErrorCodes.AUTHENTICATION_REQUIRED, cause.getCause(), errorResponse);

            default:
                return createException(cause);
        }
    }

    private DNKException createException(RetrofitError cause) {
        DNKException exception = new DNKException(cause.getResponse().getStatus(), cause.getCause());
        return exception;
    }
} public class DNKException extends Exception {

    private int errorCode;
    private Throwable exception;

    public DNKException(int errorCode, Throwable exception) {
        this.errorCode = errorCode;
        this.exception = exception;
    }

    public int getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

    public Throwable getException() {
        return exception;
    }

    public void setException(Throwable exception) {
        this.exception = exception;
    }
} public interface CloudKitService {
    @GET("/database/{version}/{container}/{environment}/{database}/zones/list")
    void getZones(@Path("version") String version, @Path("container") String container, @Path("environment") String environment, @Path("database") String database, @Query("ckAPIToken") String ckAPIToken, Callback<List<DNKZone>> callback);
    @GET("/database/{version}/{container}/{environment}/public/users/current")
    void getCurrentUser(@Path("version") String version, @Path("container") String container, @Path("environment") String environment, @Query("ckAPIToken") String ckAPIToken, Callback<DNKUser> callback);
    @POST("/database/{version}/{container}/{environment}/{database}/records/query")
    void queryRecords(@Path("version") String version, @Path("container") String container, @Path("environment") String environment, @Path("database") String database, @Body DNKRecordQueryRequest recordQueryRequest, @Query("ckAPIToken") String ckAPIToken, Callback<DNKRecordsResponse> callback);
    @POST("/database/{version}/{container}/{environment}/{database}/records/modify")
    void modifyRecords(@Path("version") String version, @Path("container") String container, @Path("environment") String environment, @Path("database") String database, @Body DNKRecordModifyRequest recordModifyRequest, @Query("ckAPIToken") String ckAPIToken, Callback<DNKRecordsResponse> callback);
    @POST("/database/{version}/{container}/{environment}/{database}/records/lookup")
    void lookupRecords(@Path("version") String version, @Path("container") String container, @Path("environment") String environment, @Path("database") String database, @Body DNKRecordLookupRequest recordLookupRequest, @Query("ckAPIToken") String ckAPIToken, Callback<DNKRecordsResponse> callback);
} public interface CloudKitWebViewRedirectHandler {
    void onRedirectFound(Uri redirectUri);
} public interface DNKCloudKitAuth {
    void onAuthSucceed();
} public abstract class DNKObject implements Serializable {

    private DNKRecord record;

    public DNKObject() {
        record = new DNKRecord();
        setDefaultUUID();
    }

    public DNKRecord toRecord() {
        DNKAnnotationParser annotationParser = new DNKAnnotationParser();
        try {
            annotationParser.parseToRecord(this, record);
        } catch (DNKRecordConversionException e) {
            e.printStackTrace();
            return null;
        }
        return record;
    }

    public List<DNKRecord> getDescendingRecords() {
        List<DNKRecord> records = new ArrayList<>();
        for(Field field : getClass().getDeclaredFields()) {
            field.setAccessible(true);
            if(field.isAnnotationPresent(CKReference.class)) {
                try {

                    List<DNKObject> references = (List<DNKObject>)field.get(this);

                    for(DNKObject object : references) {
                        records.add(object.toRecord());
                        List<DNKRecord> descendants = object.getDescendingRecords();
                        if(descendants.size() > 0)
                            records.addAll(descendants);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return records;
    }

    private void setDefaultUUID() {
        for(Field field : getClass().getDeclaredFields()) {
            field.setAccessible(true);
            if(field.isAnnotationPresent(RecordName.class)) {
                try {
                    field.set(this, UUID.randomUUID().toString());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
} public interface DNKCallback<T> {
    void success(T t);
    void failure(Throwable exception);
} public class DNKObjectProcessingCallback<T, K> implements Callback<T> {

    private List<K> responseObjects;

    @Override
    public void success(T t, Response response) {
        if(t.getClass().equals(DNKRecordsResponse.class)) {
            try {
                DNKRecordsResponse recordsResponse = (DNKRecordsResponse) t;

                responseObjects = new ArrayList<>();

                DNKAnnotationParser annotationParser = new DNKAnnotationParser();

                for (DNKRecord record : recordsResponse.getRecords()) {
                    K destinationObject = annotationParser.recordToObject(record);
                    responseObjects.add(destinationObject);
                }

            } catch(Exception e) {
                failure(null);
            }
        }
    }

    @Override
    public void failure(RetrofitError error) {

    }

    public List<K> getResponseObjects() {
        return responseObjects;
    }
} public class DNKRecordLookupRequest {
    private List<DNKLookupRecord> records;
    private DNKZone zoneID;
    private List<String> desiredKeys;

    public List<DNKLookupRecord> getRecords() {
        return records;
    }

    public void setRecords(List<DNKLookupRecord> records) {
        this.records = records;
    }

    public DNKZone getZoneID() {
        return zoneID;
    }

    public void setZoneID(DNKZone zoneID) {
        this.zoneID = zoneID;
    }

    public List<String> getDesiredKeys() {
        return desiredKeys;
    }

    public void setDesiredKeys(List<String> desiredKeys) {
        this.desiredKeys = desiredKeys;
    }

    public static DNKRecordLookupRequest createSingleRecordRequest(String recordName) {
        List<String> recordNames = new ArrayList<>();
        recordNames.add(recordName);
        return createMultipleRecordRequest(recordNames);
    }

    public static DNKRecordLookupRequest createMultipleRecordRequest(List<String> recordNames) {

        List<DNKLookupRecord> records = new ArrayList<>();

        for(String recordName : recordNames) {
            DNKLookupRecord lookupRecord = new DNKLookupRecord();
            lookupRecord.setRecordName(recordName);
            records.add(lookupRecord);
        }

        DNKRecordLookupRequest request = new DNKRecordLookupRequest();
        request.setRecords(records);

        return request;
    }
} public class DNKRecordModifyRequest {
    private List<DNKRecordOperation> operations;
    private DNKZone zoneID;
    private Boolean atomic;
    private List<String> desiredKeys;

    public List<DNKRecordOperation> getOperations() {
        return operations;
    }

    public void setOperations(List<DNKRecordOperation> operations) {
        this.operations = operations;
    }

    public DNKZone getZoneID() {
        return zoneID;
    }

    public void setZoneID(DNKZone zoneID) {
        this.zoneID = zoneID;
    }

    public Boolean getAtomic() {
        return atomic;
    }

    public void setAtomic(Boolean atomic) {
        this.atomic = atomic;
    }

    public List<String> getDesiredKeys() {
        return desiredKeys;
    }

    public void setDesiredKeys(List<String> desiredKeys) {
        this.desiredKeys = desiredKeys;
    }


    public static DNKRecordModifyRequest createRequest(DNKObject record, DNKOperationType operationType) {
        List<DNKRecord> records = new ArrayList<>();
        records.add(record.toRecord());
        return createRequest(records, operationType);
    }


    public static DNKRecordModifyRequest createRequest(List<DNKRecord> records, DNKOperationType operationType) {

        List<DNKRecordOperation> operations = new ArrayList<>();

        for(DNKRecord record : records) {
            DNKRecordOperation recordOperation = new DNKRecordOperation();
            recordOperation.setOperationType(operationType.toString());
            recordOperation.setRecord(record);
            operations.add(recordOperation);
        }

        DNKRecordModifyRequest request = new DNKRecordModifyRequest();

        request.setOperations(operations);

        return request;
    }

} public class DNKRecordQueryRequest {

    private DNKZone zoneID;
    private String resultsLimit;
    private DNKQuery query;
    //TODO continuationMarker
    //TODO desiredKeys
    //TODO zoneWide


    public DNKZone getZoneID() {
        return zoneID;
    }

    public void setZoneID(DNKZone zoneID) {
        this.zoneID = zoneID;
    }

    public String getResultsLimit() {
        return resultsLimit;
    }

    public void setResultsLimit(String resultsLimit) {
        this.resultsLimit = resultsLimit;
    }

    public DNKQuery getQuery() {
        return query;
    }

    public void setQuery(DNKQuery query) {
        this.query = query;
    }

    public static DNKRecordQueryRequest queryWithRecordName(String recordType) {
        DNKQuery query = new DNKQuery();
        query.setRecordType(recordType);

        DNKRecordQueryRequest recordQueryRequest = new DNKRecordQueryRequest();
        recordQueryRequest.setZoneID(new DNKZone());
        recordQueryRequest.setQuery(query);
        return recordQueryRequest;
    }
} public class DNKRecord implements Serializable {
    
    private String recordName;
    private String recordType;
    private String recordChangeTag;

    @SerializedName("fields")
    private Map<String, DNKRecordField> fields = new HashMap<>();

    private Boolean deleted;

    public String getRecordName() {
        return recordName;
    }

    public void setRecordName(String recordName) {
        this.recordName = recordName;
    }

    public String getRecordType() {
        return recordType;
    }

    public void setRecordType(String recordType) {
        this.recordType = recordType;
    }

    public String getRecordChangeTag() {
        return recordChangeTag;
    }

    public void setRecordChangeTag(String recordChangeTag) {
        this.recordChangeTag = recordChangeTag;
    }

    public Map<String, DNKRecordField> getFields() {
        return fields;
    }

    public void setFields(Map<String, DNKRecordField> fields) {
        this.fields = fields;
    }

    public Boolean getDeleted() {
        return deleted;
    }

    public void setDeleted(Boolean deleted) {
        this.deleted = deleted;
    }
} public class DNKRecordField<T> implements Serializable {
    private T value;
    private DNKFieldTypes type;

    public DNKRecordField(DNKFieldTypes type, T value) {
        this.type = type;
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    public DNKFieldTypes getType() {
        return type;
    }

    public void setType(DNKFieldTypes type) {
        this.type = type;
    }

} public class DNKRecordsResponse {

    private List<DNKRecord> records;

    public List<DNKRecord> getRecords() {
        return records;
    }

    public void setRecords(List<DNKRecord> records) {
        this.records = records;
    }
} public class DNKUser {
    private String userRecordName;
    private String firstName;
    private String lastName;

    public String getUserRecordName() {
        return userRecordName;
    }

    public void setUserRecordName(String userRecordName) {
        this.userRecordName = userRecordName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
} public class DNKZone implements Serializable {
    private String zoneName = "_defaultZone";

    public String getZoneName() {
        return zoneName;
    }

    public void setZoneName(String zoneName) {
        this.zoneName = zoneName;
    }
} public class DNKWebViewAuthActivity extends Activity {

    private String urlRedirectPattern;

    private CloudKitWebViewRedirectHandler redirectHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        redirectHandler = DroidNubeKit.getInstance();

        final WebView webView = new WebView(this);
        setContentView(webView);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                if(url != null && url.startsWith(urlRedirectPattern)) {
                    Log.d("CK", "Redirect found");
                    Uri redirectUri = Uri.parse(url);
                    redirectHandler.onRedirectFound(redirectUri);
                    finish();
                    return;
                }

                Log.d("CK", "Starting to load :"+url+" - "+getUrlRedirectPattern());
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                Log.d("CK", "Finishing to load: "+url);
            }

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                if(url.startsWith(urlRedirectPattern)) {
                    Uri redirectUri = Uri.parse(url);
                    redirectHandler.onRedirectFound(redirectUri);
                    finish();
                    return true;
                }
                return super.shouldOverrideUrlLoading(view, url);
            }
        });
        String redirectURL = getIntent().getExtras().getString(DroidNubeKitConstants.WEBVIEW_REDIRECT_URL_EXTRA);
        setUrlRedirectPattern(getIntent().getExtras().getString(DroidNubeKitConstants.WEBVIEW_REDIRECT_PATTERN_EXTRA));
        webView.loadUrl(redirectURL);
    }

    public String getUrlRedirectPattern() {
        return urlRedirectPattern;
    }

    public void setUrlRedirectPattern(String urlRedirectPattern) {
        this.urlRedirectPattern = urlRedirectPattern;
    }
} public enum DNKOperationType {
    CREATE("create"),
    UPDATE("update"),
    FORCE_UPDATE("forceUpdate"),
    REPLACE("replace"),
    FORCE_REPLACE("forceReplace"),
    DELETE("delete"),
    FORCE_DELETE("forceDelete");

    private final String text;

    /**
     * @param text
     */
    private DNKOperationType(final String text) {
        this.text = text;
    }

    /* (non-Javadoc)
     * @see java.lang.Enum#toString()
     */
    @Override
    public String toString() {
        return text;
    }

    public static DNKOperationType fromString(String stringtype) {
        for(DNKOperationType type : DNKOperationType.values()) {
            if(type.toString().equals(stringtype))
                return type;
        }

        return null;
    }
} public class DNKRecordFieldDeserializer implements JsonDeserializer<Map<String, DNKRecordField>> {
    @Override
    public Map<String, DNKRecordField> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        Map<String, DNKRecordField> field = new HashMap<String, DNKRecordField>();

        JsonObject contents = json.getAsJsonObject();

        for(Map.Entry<String,JsonElement> entry  : contents.entrySet()) {
            DNKRecordField recordField;

            String type = entry.getValue().getAsJsonObject().get("type").getAsString();

            switch (DNKFieldTypes.fromString(type)) {
                case STRING:
                    recordField = new DNKRecordField<String>(DNKFieldTypes.STRING, entry.getValue().getAsJsonObject().get("value").getAsString());
                    break;
                case INT64:
                    recordField = new DNKRecordField<BigInteger>(DNKFieldTypes.INT64, entry.getValue().getAsJsonObject().get("value").getAsBigInteger());
                    break;
                case TIMESTAMP:
                    Timestamp ts = new Timestamp(entry.getValue().getAsJsonObject().get("value").getAsBigInteger().longValue());
                    Date date = new Date(ts.getTime());
                    recordField = new DNKRecordField<Date>(DNKFieldTypes.TIMESTAMP, date);
                    break;
                case REFERENCE:
                    DNKReference singleReference = new DNKReference();
                    JsonObject value = entry.getValue().getAsJsonObject().get("value").getAsJsonObject();
                    singleReference.setRecordName(value.get("recordName").getAsString());
                    singleReference.setAction(value.get("action").getAsString());
                    recordField = new DNKRecordField<DNKReference>(DNKFieldTypes.REFERENCE, singleReference);
                    break;
                case REFERENCE_LIST:
                    JsonArray values = entry.getValue().getAsJsonObject().get("value").getAsJsonArray();

                    List<DNKReference> references = new ArrayList<>();

                    for(int i = 0; i < values.size(); i++) {
                        JsonElement referenceObject = values.get(i);
                        DNKReference reference = new DNKReference();
                        reference.setRecordName(referenceObject.getAsJsonObject().get("recordName").getAsString());
                        //TODO parse zones
                        reference.setAction(referenceObject.getAsJsonObject().get("action").getAsString());

                        references.add(reference);
                    }

                    recordField = new DNKRecordField<List<DNKReference>>(DNKFieldTypes.REFERENCE_LIST, references);
                    break;
                default:
                    recordField = null;
                    break;
            }

            field.put(entry.getKey(), recordField);
        }

        return field;
    }
}
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.preference.PreferenceManager;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import net.moddity.droidnubekit.annotations.RecordType;
import net.moddity.droidnubekit.errors.DNKErrorHandler;
import net.moddity.droidnubekit.errors.DNKException;
import net.moddity.droidnubekit.interfaces.CloudKitService;
import net.moddity.droidnubekit.interfaces.CloudKitWebViewRedirectHandler;
import net.moddity.droidnubekit.interfaces.DNKCloudKitAuth;
import net.moddity.droidnubekit.objects.DNKObject;
import net.moddity.droidnubekit.requests.DNKCallback;
import net.moddity.droidnubekit.requests.DNKObjectProcessingCallback;
import net.moddity.droidnubekit.requests.DNKRecordLookupRequest;
import net.moddity.droidnubekit.requests.DNKRecordModifyRequest;
import net.moddity.droidnubekit.requests.DNKRecordQueryRequest;
import net.moddity.droidnubekit.responsemodels.DNKRecord;
import net.moddity.droidnubekit.responsemodels.DNKRecordField;
import net.moddity.droidnubekit.responsemodels.DNKRecordsResponse;
import net.moddity.droidnubekit.responsemodels.DNKUser;
import net.moddity.droidnubekit.responsemodels.DNKZone;
import net.moddity.droidnubekit.ui.DNKWebViewAuthActivity;
import net.moddity.droidnubekit.utils.DNKOperationType;
import net.moddity.droidnubekit.utils.DNKRecordFieldDeserializer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import dalvik.system.DexFile;
import retrofit.Callback;
import retrofit.RequestInterceptor;
import retrofit.RestAdapter;
import retrofit.RetrofitError;
import retrofit.client.Response;
import retrofit.converter.GsonConverter;
package net.moddity.droidnubekit;










/**
 * Created by Jaume Cornadó on 11/6/15.
 */
public class DroidNubeKit implements CloudKitWebViewRedirectHandler {

    /** The singleton instance */
    private static DroidNubeKit instance;

    /** The CloudKit Service reference */
    private CloudKitService cloudKitService;

    /** The API Token */
    private String apiToken;

    /** Session on auth */
    private String ckSession;

    /** If it's production or development */
    private DroidNubeKitConstants.kEnvironmentType environmentType;

    private String appContainerIdentifier;

    private Context context;

    /** External redirect handler to control authentication */
    private DNKCloudKitAuth cloudKitAuthHandler;

    private DNKUser currentUser;

    public Set<Class<?>> modelClasses = new HashSet<>();


    /**
     * Initializes the CloudKit Service
     */
    private DroidNubeKit() {

        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(new TypeToken<Map<String, DNKRecordField>>() {}.getType(), new DNKRecordFieldDeserializer());

        RequestInterceptor requestInterceptor = new RequestInterceptor() {
            @Override
            public void intercept(RequestInterceptor.RequestFacade request) {
                if(ckSession != null)
                    request.addQueryParam("ckSession", ckSession);

            }
        };

        RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(DroidNubeKitConstants.API_ENDPOINT)
                .setLogLevel(RestAdapter.LogLevel.FULL)
                .setConverter(new GsonConverter(gsonBuilder.create()))
                .setRequestInterceptor(requestInterceptor)
                .setErrorHandler(new DNKErrorHandler())
                .build();

        cloudKitService = restAdapter.create(CloudKitService.class);
    }

    /**
     * Singleton instance
     * @return the instance
     */
    public static DroidNubeKit getInstance() {
        if(instance == null)
            instance = new DroidNubeKit();
        return instance;
    }

    //----------------------
    // Public methods
    //----------------------

    /**
     * The method to initialize the library
     * @param apiToken Api Token obtained on CloudKit dashboard
     * @param appContainerIdentifier Your App Container identifier iCloud.net.moddity.yourapp (similar to this)
     * @param environmentType development / production environment
     * @param context Pass a context to the lib
     */
    public static void initNube(String apiToken, String appContainerIdentifier, DroidNubeKitConstants.kEnvironmentType environmentType, Context context) {
        DroidNubeKit.getInstance().apiToken = apiToken;
        DroidNubeKit.getInstance().environmentType = environmentType;
        DroidNubeKit.getInstance().appContainerIdentifier = appContainerIdentifier;
        DroidNubeKit.getInstance().context = context;
        DroidNubeKit.getInstance().checkForSession();

        try {
            DroidNubeKit.getInstance().modelClasses = DroidNubeKit.getInstance().getClasspathClasses();
        } catch (Exception e) {
            //Todo throw inizialization exception
            e.printStackTrace();
        }
    }

    /**
     * Fetch records using a query
     * @param queryRequest The query request object
     * @param environmentType development / production environment
     * @param callback callback to process the result
     */
    public static <T> void fetchRecordsByQuery(DNKRecordQueryRequest queryRequest, DroidNubeKitConstants.kDatabaseType environmentType, final DNKCallback<List<T>> callback) {
        DroidNubeKit.getInstance().cloudKitService.queryRecords(
                DroidNubeKitConstants.PROTOCOL,
                DroidNubeKit.getInstance().appContainerIdentifier,
                DroidNubeKit.getInstance().environmentType.toString(),
                environmentType.toString(),
                queryRequest,
                DroidNubeKit.getInstance().apiToken,
                new DNKObjectProcessingCallback<DNKRecordsResponse, T>() {