org.devnexus.aerogear.ShadowStore.java Source code

Java tutorial

Introduction

Here is the source code for org.devnexus.aerogear.ShadowStore.java

Source

/**
 * JBoss, Home of Professional Open Source
 * Copyright Red Hat, Inc., and individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.devnexus.aerogear;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.AsyncTask;
import android.util.Log;
import android.util.Pair;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;

import org.devnexus.DevnexusApplication;
import org.jboss.aerogear.android.Callback;
import org.jboss.aerogear.android.ReadFilter;
import org.jboss.aerogear.android.datamanager.IdGenerator;
import org.jboss.aerogear.android.datamanager.Store;
import org.jboss.aerogear.android.datamanager.StoreType;
import org.jboss.aerogear.android.impl.datamanager.DefaultIdGenerator;
import org.jboss.aerogear.android.impl.datamanager.SQLStore;
import org.jboss.aerogear.android.impl.datamanager.StoreTypes;
import org.jboss.aerogear.android.impl.reflection.Property;
import org.jboss.aerogear.android.impl.reflection.Scan;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * This class is the same as SQLStore except it appends 'shadow_' to the table name.
 * <p/>
 * It is used by the UserCalendarSynchronizer and will be removed once we fix JIRA AGDROID-175
 */
public class ShadowStore<T> extends SQLiteOpenHelper implements Store<T> {

    private static final String TAG = SQLStore.class.getSimpleName();
    private final Class<T> klass;
    private final String className;
    private final static String CREATE_PROPERTIES_TABLE = "create table if not exists shadow_%s_property "
            + " ( _ID integer primary key autoincrement," + "  PARENT_ID text not null,"
            + "  PROPERTY_NAME text not null," + "  PROPERTY_VALUE text )";
    private final static String CREATE_PROPERTIES_INDEXES = "create index  if not exists shadow_%s_property_name_index "
            + " ON shadow_%s_property (PROPERTY_NAME);"
            + "create index if not exists shadow_%s_property_name_value_index "
            + " ON shadow_%s_property (PROPERTY_NAME, PROPERTY_VALUE) ;"
            + "create index  if not exists shadow_%s_property_parent_index "
            + " ON shadow_%s_property (PARENT_ID);";
    private SQLiteDatabase database;
    private final Gson gson;
    private final IdGenerator generator;

    public ShadowStore(Class<T> klass, Context context) {
        super(context, "Shadow_" + klass.getSimpleName(), null, 1);
        this.klass = klass;
        this.className = klass.getSimpleName();
        this.gson = new Gson();
        this.generator = new DefaultIdGenerator();
    }

    public ShadowStore(Class<T> klass, Context context, GsonBuilder builder, IdGenerator generator) {
        super(context, "Shadow_" + klass.getSimpleName(), null, 1);
        this.klass = klass;
        this.className = klass.getSimpleName();
        this.gson = builder.create();
        this.generator = generator;
    }

    /**
     * {@inheritDoc }
     */
    @Override
    public StoreType getType() {
        return StoreTypes.SQL;
    }

    /**
     * {@inheritDoc }
     */
    @Override
    public Collection<T> readAll() {
        String sql = String.format("Select PROPERTY_NAME, PROPERTY_VALUE,PARENT_ID from shadow_%s_property",
                className);
        Cursor cursor = getDatabase().rawQuery(sql, new String[0]);
        HashMap<Integer, JsonObject> objects = new HashMap<Integer, JsonObject>(cursor.getCount());
        try {
            while (cursor != null && cursor.moveToNext()) {
                Integer id = cursor.getInt(2);
                JsonObject object = objects.get(id);
                if (object == null) {
                    object = new JsonObject();
                    objects.put(id, object);
                }
                add(object, cursor.getString(0), cursor.getString(1));
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        ArrayList<T> data = new ArrayList<T>(cursor.getCount());
        for (JsonObject object : objects.values()) {
            data.add(gson.fromJson(object, klass));
        }

        return data;

    }

    /**
     * {@inheritDoc }
     */
    @Override
    public T read(Serializable id) {
        String sql = String.format(
                "Select PROPERTY_NAME, PROPERTY_VALUE from shadow_%s_property where PARENT_ID = ?", className);
        String[] bindArgs = new String[1];
        bindArgs[0] = id.toString();
        JsonObject result = new JsonObject();
        Cursor cursor = getDatabase().rawQuery(sql, bindArgs);

        if (cursor.getCount() == 0) {
            return null;
        }

        try {
            while (cursor != null && cursor.moveToNext()) {
                add(result, cursor.getString(0), cursor.getString(1));
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }

        return gson.fromJson(result, klass);

    }

    /**
     * {@inheritDoc }
     */
    @Override
    public List<T> readWithFilter(ReadFilter filter) {
        if (filter == null) {
            filter = new ReadFilter();
        }
        String sql = String.format(
                "select PARENT_ID from shadow_%s_property where PROPERTY_NAME = ? and PROPERTY_VALUE = ?",
                className);
        JsonObject where = (JsonObject) new JsonParser().parse(filter.getWhere().toString());
        List<Pair<String, String>> queryList = new ArrayList<Pair<String, String>>();
        Map<String, AtomicInteger> resultCount = new HashMap<String, AtomicInteger>();
        buildKeyValuePairs(where, queryList, "");

        if (queryList.isEmpty()) {//there is no query
            return new ArrayList<T>(readAll());
        } else {
            for (Pair<String, String> kv : queryList) {
                String[] bindArgs = new String[] { kv.first, kv.second };
                Cursor cursor = null;
                try {
                    cursor = getDatabase().rawQuery(sql, bindArgs);
                    while (cursor != null && cursor.moveToNext()) {
                        String id = cursor.getString(0);
                        AtomicInteger count = resultCount.get(id);
                        if (count == null) {
                            count = new AtomicInteger(0);
                            resultCount.put(id, count);
                        }
                        count.incrementAndGet();
                    }
                } finally {
                    if (cursor != null) {
                        cursor.close();
                    }
                }
            }
        }
        List<T> results = new ArrayList<T>();

        for (String id : resultCount.keySet()) {
            if (resultCount.get(id).get() == queryList.size()) {//There are as many objects as queries which meant a result was returned for every query
                results.add(read(id));
            }
        }

        return results;
    }

    /**
     * {@inheritDoc }
     */
    @Override
    public void save(T item) {
        String recordIdFieldName = Scan.recordIdFieldNameIn(item.getClass());
        Property property = new Property(item.getClass(), recordIdFieldName);
        Serializable idValue = (Serializable) property.getValue(item);

        if (idValue == null) {
            idValue = generator.generate();
            property.setValue(item, idValue);
        }

        JsonObject serialized = (JsonObject) gson.toJsonTree(item, klass);
        getDatabase().beginTransaction();
        try {
            saveElement(serialized, "", idValue);
            getDatabase().setTransactionSuccessful();
        } finally {
            getDatabase().endTransaction();
        }
    }

    private void saveElement(JsonObject serialized, String path, Serializable id) {
        String sql = String.format(
                "insert into shadow_%s_property (PROPERTY_NAME, PROPERTY_VALUE, PARENT_ID) values (?,?,?)",
                className);
        Set<Map.Entry<String, JsonElement>> members = serialized.entrySet();
        String pathVar = path.isEmpty() ? "" : ".";
        for (Map.Entry<String, JsonElement> member : members) {
            JsonElement jsonValue = member.getValue();
            String propertyName = member.getKey();
            if (jsonValue.isJsonObject()) {
                saveElement((JsonObject) jsonValue, path + pathVar + propertyName, id);
            } else if (jsonValue.isJsonArray()) {
                JsonArray jsonArray = jsonValue.getAsJsonArray();
                for (int index = 0; index < jsonArray.size(); index++) {
                    JsonElement arrayElement = jsonArray.get(index);
                    if (arrayElement.isJsonPrimitive()) {
                        JsonPrimitive primitive = arrayElement.getAsJsonPrimitive();
                        if (primitive.isBoolean()) {
                            String value = primitive.getAsBoolean() ? "true" : "false";
                            getDatabase().execSQL(sql, new Object[] {
                                    path + pathVar + propertyName + String.format("[%d]", index), value, id });
                        } else if (primitive.isNumber()) {
                            Number value = primitive.getAsNumber();
                            getDatabase().execSQL(sql, new Object[] {
                                    path + pathVar + propertyName + String.format("[%d]", index), value, id });
                        } else if (primitive.isString()) {
                            String value = primitive.getAsString();
                            getDatabase().execSQL(sql, new Object[] {
                                    path + pathVar + propertyName + String.format("[%d]", index), value, id });
                        } else {
                            throw new IllegalArgumentException(
                                    arrayElement + " isn't a number, boolean, or string");
                        }

                    } else {
                        saveElement(arrayElement.getAsJsonObject(),
                                path + pathVar + propertyName + String.format("[%d]", index), id);
                    }

                }
            } else {
                if (jsonValue.isJsonPrimitive()) {
                    JsonPrimitive primitive = jsonValue.getAsJsonPrimitive();
                    if (primitive.isBoolean()) {
                        String value = primitive.getAsBoolean() ? "true" : "false";
                        getDatabase().execSQL(sql, new Object[] { path + pathVar + propertyName, value, id });
                    } else if (primitive.isNumber()) {
                        Number value = primitive.getAsNumber();
                        getDatabase().execSQL(sql, new Object[] { path + pathVar + propertyName, value, id });
                    } else if (primitive.isString()) {
                        String value = primitive.getAsString();
                        getDatabase().execSQL(sql, new Object[] { path + pathVar + propertyName, value, id });
                    } else {
                        throw new IllegalArgumentException(jsonValue + " isn't a number, boolean, or string");
                    }

                } else {
                    throw new IllegalArgumentException(jsonValue + " isn't a JsonPrimitive");
                }

            }
        }
    }

    /**
     * {@inheritDoc }
     */
    @Override
    public void reset() {
        String sql = String.format("Delete from shadow_%s_property", className);
        getDatabase().execSQL(sql);
    }

    /**
     * {@inheritDoc }
     */
    @Override
    public boolean isEmpty() {
        String sql = String.format("Select count(_ID) from shadow_%s_property", className);
        Cursor cursor = getDatabase().rawQuery(sql, null);
        cursor.moveToFirst();
        boolean result = (cursor.getInt(0) == 0);
        cursor.close();
        return result;
    }

    /**
     * {@inheritDoc }
     */
    @Override
    public void remove(Serializable id) {
        String sql = String.format("Delete from shadow_%s_property where PARENT_ID = ?", className);
        Object[] bindArgs = new Object[1];
        bindArgs[0] = id;
        getDatabase().execSQL(sql, bindArgs);

    }

    /**
     * {@inheritDoc }
     */
    @Override
    public void onCreate(SQLiteDatabase db) {

        db.execSQL(String.format(CREATE_PROPERTIES_TABLE, className));
        db.execSQL(String.format(CREATE_PROPERTIES_INDEXES, className, className, className, className, className,
                className));
    }

    /**
     * {@inheritDoc }
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }

    public void open(final Callback<ShadowStore<T>> onReady) {
        new AsyncTask<Void, Void, Void>() {
            private Exception exception;

            @Override
            protected Void doInBackground(Void... params) {
                try {
                    ShadowStore.this.database = getWritableDatabase();
                } catch (Exception e) {
                    this.exception = e;
                    Log.e(TAG, "There was an error loading the database", e);
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void result) {
                if (exception != null) {
                    onReady.onFailure(exception);
                } else {
                    onReady.onSuccess(ShadowStore.this);
                }
            }
        }.executeOnExecutor(DevnexusApplication.EXECUTORS);
    }

    @Override
    public void close() {
        this.getDatabase().close();
    }

    public synchronized SQLiteDatabase getDatabase() {
        if (!database.isOpen()) {
            database = getWritableDatabase();
        }
        return database;
    }

    private void add(JsonObject result, String propertyName, String propertyValue) {
        if (!propertyName.contains(".")) {
            if (propertyName.contains("[")) {
                String unArrayPropertyName = propertyName.split("\\[")[0];
                if (!result.has(unArrayPropertyName)) {
                    result.add(unArrayPropertyName, new JsonArray());
                }
                JsonArray array = result.getAsJsonArray(unArrayPropertyName);
                array.add(gson.toJsonTree(propertyValue));
            } else {
                result.addProperty(propertyName, propertyValue);
            }
        } else {
            String[] names = propertyName.split("\\.", 2);

            if (names[0].contains("[")) {
                String key = names[0].split("\\[")[0];
                Integer index = Integer.parseInt(names[0].split("\\[")[1].split("\\]")[0]);
                JsonArray subObject = result.getAsJsonArray(key);

                if (subObject == null) {
                    subObject = new JsonArray();
                    result.add(key, subObject);
                }

                if ((index) >= subObject.size()) {
                    for (int i = subObject.size(); i < (index + 1); i++) {
                        subObject.add(new JsonObject());
                    }
                }

                JsonObject arrayItem = subObject.get(index).getAsJsonObject();

                add(arrayItem, names[1], propertyValue);

            } else {

                JsonObject subObject = (JsonObject) result.get(names[0]);
                if (subObject == null) {
                    subObject = new JsonObject();
                    result.add(names[0], subObject);
                }

                add(subObject, names[1], propertyValue);
            }
        }
    }

    private void buildKeyValuePairs(JsonObject where, List<Pair<String, String>> keyValues, String parentPath) {
        Set<Map.Entry<String, JsonElement>> keys = where.entrySet();
        String pathVar = parentPath.isEmpty() ? "" : ".";//Set a dot if parent path is not empty
        for (Map.Entry<String, JsonElement> entry : keys) {
            String key = entry.getKey();
            String path = parentPath + pathVar + key;
            JsonElement jsonValue = entry.getValue();
            if (jsonValue.isJsonObject()) {
                buildKeyValuePairs((JsonObject) jsonValue, keyValues, path);
            } else {
                if (jsonValue.isJsonPrimitive()) {
                    JsonPrimitive primitive = jsonValue.getAsJsonPrimitive();
                    if (primitive.isBoolean()) {
                        String value = primitive.getAsBoolean() ? "true" : "false";
                        keyValues.add(new Pair<String, String>(path, value));
                    } else if (primitive.isNumber()) {
                        Number value = primitive.getAsNumber();
                        keyValues.add(new Pair<String, String>(path, value.toString()));
                    } else if (primitive.isString()) {
                        String value = primitive.getAsString();
                        keyValues.add(new Pair<String, String>(path, value));
                    } else {
                        throw new IllegalArgumentException(jsonValue + " isn't a number, boolean, or string");
                    }

                } else {
                    throw new IllegalArgumentException(jsonValue + " isn't a JsonPrimitive");
                }

            }
        }
    }

}