{"action":"created","comment":{"url":"https://api.github.com/repos/ActiveStack/syncengine/pulls/comments/40369473","id":40369473,"diff_hunk":"@@ -0,0 +1,1279 @@\n+package com.percero.agents.sync.services;\n+\n+import java.lang.reflect.InvocationTargetException;\n+import java.lang.reflect.Method;\n+import java.util.ArrayList;\n+import java.util.Collection;\n+import java.util.HashMap;\n+import java.util.HashSet;\n+import java.util.Iterator;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.Map.Entry;\n+import java.util.Set;\n+import java.util.UUID;\n+import java.util.concurrent.TimeUnit;\n+\n+import javassist.ClassPool;\n+import javassist.CtClass;\n+import javassist.CtField;\n+import javassist.CtMethod;\n+\n+import org.apache.log4j.Logger;\n+import org.codehaus.jackson.map.ObjectMapper;\n+import org.hibernate.PropertyValueException;\n+import org.hibernate.Query;\n+import org.hibernate.type.Type;\n+import org.springframework.beans.factory.annotation.Autowired;\n+import org.springframework.stereotype.Component;\n+import org.springframework.util.StringUtils;\n+\n+import com.percero.agents.sync.access.RedisKeyUtils;\n+import com.percero.agents.sync.dao.DAORegistry;\n+import com.percero.agents.sync.dao.IDataAccessObject;\n+import com.percero.agents.sync.datastore.ICacheDataStore;\n+import com.percero.agents.sync.exceptions.SyncDataException;\n+import com.percero.agents.sync.exceptions.SyncException;\n+import com.percero.agents.sync.metadata.IMappedClassManager;\n+import com.percero.agents.sync.metadata.MappedClass;\n+import com.percero.agents.sync.metadata.MappedClassManagerFactory;\n+import com.percero.agents.sync.metadata.MappedField;\n+import com.percero.agents.sync.metadata.MappedFieldList;\n+import com.percero.agents.sync.metadata.MappedFieldPerceroObject;\n+import com.percero.agents.sync.vo.BaseDataObject;\n+import com.percero.agents.sync.vo.ClassIDPair;\n+import com.percero.agents.sync.vo.ClassIDPairs;\n+import com.percero.agents.sync.vo.IJsonObject;\n+import com.percero.framework.vo.IPerceroObject;\n+import com.percero.framework.vo.PerceroList;\n+\n+@Component\n+public class DAODataProvider implements IDataProvider {\n+\n+\t// TODO: Better manage Hibernate Sessions (opening and closing).\n+\n+\tprivate static final Logger log = Logger.getLogger(DAODataProvider.class);\n+\t\n+\tprivate static DAODataProvider instance = null;\n+\t\n+\tpublic static DAODataProvider getInstance() {\n+//\t\tif (instance == null) {\n+//\t\t\tinstance = new DAODataProvider();\n+//\t\t\tinstance.initialize();\n+//\t\t}\n+\t\treturn instance;\n+\t}\n+\t\n+\tpublic DAODataProvider() {\n+\t\tinstance = this;\n+\t\tinitialize();\n+\t}\n+\n+\tpublic void initialize()\n+\t{\n+\t\t// Do nothing.\n+\t}\n+\t\n+\tpublic String getName() {\n+\t\treturn \"daoDataProvider\";\n+\t}\n+\t\n+\t@Autowired\n+\tIDataProviderManager dataProviderManager;\n+\tpublic void setDataProviderManager(IDataProviderManager value) {\n+\t\tdataProviderManager = value;\n+\t}\n+\t\n+\t@Autowired\n+\tICacheDataStore cacheDataStore;\n+\t\n+\t@Autowired\n+\tLong cacheTimeout = Long.valueOf(60 * 60 * 24 * 14);\t// Two weeks\n+\n+\t@Autowired\n+\tObjectMapper safeObjectMapper;\n+\n+\t\n+\n+\t@SuppressWarnings({ \"unchecked\" })\n+\t// TODO: @Transactional(readOnly=true)\n+\tpublic PerceroList<IPerceroObject> getAllByName(String className, Integer pageNumber, Integer pageSize, Boolean returnTotal, String userId) throws Exception {\n+\t\tIDataAccessObject<IPerceroObject> dao = (IDataAccessObject<IPerceroObject>) DAORegistry.getInstance().getDataAccessObject(className);\n+\t\tPerceroList<IPerceroObject> results = dao.getAll(pageNumber, pageSize, returnTotal, userId, false);\n+\t\t\n+\t\tif (results != null && !results.isEmpty()) {\n+\t\t\tIterator<? extends IPerceroObject> itrResults = results.iterator();\n+\t\t\twhile (itrResults.hasNext()) {\n+\t\t\t\tIPerceroObject nextResult = itrResults.next();\n+\t\t\t\ttry {\n+\t\t\t\t\tpopulateToManyRelationships(nextResult, true, null);\n+\t\t\t\t\tpopulateToOneRelationships(nextResult, true, null);\n+\t\t\t\t} catch (IllegalArgumentException e) {\n+\t\t\t\t\te.printStackTrace();\n+\t\t\t\t\tthrow new SyncDataException(e);\n+\t\t\t\t} catch (IllegalAccessException e) {\n+\t\t\t\t\te.printStackTrace();\n+\t\t\t\t\tthrow new SyncDataException(e);\n+\t\t\t\t} catch (InvocationTargetException e) {\n+\t\t\t\t\te.printStackTrace();\n+\t\t\t\t\tthrow new SyncDataException(e);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\t\n+\t\tputObjectsInRedisCache(results);\n+\t\t\n+\t\t// Now clean the objects for the user.\n+\t\tList<IPerceroObject> cleanedObjects = cleanObject(results, userId);\n+\t\tresults.clear();\n+\t\tresults.addAll(cleanedObjects);\n+\t\treturn results;\n+\t}\n+\n+\t@SuppressWarnings(\"unchecked\")\n+\tpublic Set<ClassIDPair> getAllClassIdPairsByName(String className) throws Exception {\n+\t\tIDataAccessObject<IPerceroObject> dao = (IDataAccessObject<IPerceroObject>) DAORegistry.getInstance().getDataAccessObject(className);\n+\t\tPerceroList<IPerceroObject> allObjects = dao.getAll(null, null, false, null, true);\n+\t\t\n+\t\tSet<ClassIDPair> results = new HashSet<ClassIDPair>(allObjects == null ? 0 : allObjects.size());\n+\t\t\n+\t\tif (allObjects != null && !allObjects.isEmpty()) {\n+\t\t\tIterator<? extends IPerceroObject> itrResults = allObjects.iterator();\n+\t\t\twhile (itrResults.hasNext()) {\n+\t\t\t\tIPerceroObject nextResult = itrResults.next();\n+\t\t\t\tresults.add(BaseDataObject.toClassIdPair(nextResult));\n+\t\t\t}\n+\t\t}\n+\t\t\n+\t\treturn results;\n+\t}\n+\t\n+\t@SuppressWarnings({ \"unchecked\" })\n+\t// TODO: @Transactional(readOnly=true)\n+\tpublic Integer countAllByName(String className, String userId) throws Exception {\n+\t\tIDataAccessObject<IPerceroObject> dao = (IDataAccessObject<IPerceroObject>) DAORegistry.getInstance().getDataAccessObject(className);\n+\t\tInteger result = dao.countAll(userId);\n+\t\treturn result;\n+\t}\n+\n+\t@SuppressWarnings(\"unchecked\")\n+\tpublic List<Object> runQuery(MappedClass mappedClass, String queryName, Object[] queryArguments, String userId) throws SyncException {\n+\t\tIDataAccessObject<IPerceroObject> dao = (IDataAccessObject<IPerceroObject>) DAORegistry.getInstance().getDataAccessObject(mappedClass.className);\n+\t\tList<Object> result = dao.runQuery(queryName, queryArguments, userId);\n+\t\treturn result;\n+\t}\n+\t\n+\t// TODO: This method has not been tested and is most likely broken.\n+\t@SuppressWarnings({ \"rawtypes\", \"unchecked\" })\n+\tprotected static List<Object> processQueryResults(String resultClassName, Query updateFilter, List updateFilterResult) throws Exception {\n+\t\tString[] returnAliases = updateFilter.getReturnAliases();\n+\t\tType[] returnTypes = updateFilter.getReturnTypes();\n+\t\t\n+\t\tString[] fieldNames = new String[returnAliases.length];\n+\t\tString[] getMethodNames = new String[returnAliases.length];\n+\t\tString[] setMethodNames = new String[returnAliases.length];\n+\t\tClass[] fieldClasses = new Class[returnAliases.length];\n+\t\t\n+\t\tClass clazz = null;\n+\t\tClassPool pool = null;\n+\t\tCtClass evalClass = null;\n+\t\t\n+\t\tif (returnAliases.length > 1) {\n+\t\t\ttry {\n+\t\t\t\tclazz = MappedClass.forName(resultClassName);\n+\t\t\t} catch(Exception e) {\n+\t\t\t\t// Class must not yet exist, so let's create it.\n+\t\t\t}\n+\t\t\t\n+\t\t\tif (clazz == null) {\n+\t\t\t\tpool = ClassPool.getDefault();\n+\t\t\t\tevalClass = pool.makeClass(resultClassName);\n+\t\t\t}\n+\t\t\t\t\n+\t\t\t// Create a new Class based on the result set.\n+\t\t\tfor(int i = 0; i < returnAliases.length; i++) {\n+\t\t\t\tType nextType = returnTypes[i];\n+\t\t\t\tString nextTypeCanonicalName = nextType.getReturnedClass().getCanonicalName();\n+\t\t\t\tString nextFieldName = returnAliases[i];\n+\t\t\t\ttry {\n+\t\t\t\t\tInteger.parseInt(nextFieldName);\n+\t\t\t\t\tnextFieldName = \"field\" + i;\n+\t\t\t\t} catch(NumberFormatException nfe) {\n+\t\t\t\t\t// Do nothing. Simply means the field name is not a Number.\n+\t\t\t\t}\n+\t\t\t\tString nextUpperFieldName = nextFieldName.substring(0, 1).toUpperCase() + nextFieldName.substring(1);\n+\t\t\t\t\n+\t\t\t\tfieldNames[i] = nextFieldName;\n+\t\t\t\tgetMethodNames[i] = \"get\" + nextUpperFieldName;\n+\t\t\t\tsetMethodNames[i] = \"set\" + nextUpperFieldName;\n+\t\t\t\tfieldClasses[i] = nextType.getReturnedClass();\n+\t\t\t\t\n+\t\t\t\tif (evalClass != null) {\n+\t\t\t\t\tevalClass.addField(CtField.make(\"private \" + fieldClasses[i].getCanonicalName() + \" \" + nextFieldName + \";\", evalClass));\n+\t\t\t\t\tevalClass.addMethod(CtMethod.make(\"public void \" + setMethodNames[i] + \"(\" + fieldClasses[i].getCanonicalName() + \" value) {this.\" + nextFieldName + \" = value;}\", evalClass));\n+\t\t\t\t\tevalClass.addMethod(CtMethod.make(\"public \" + nextTypeCanonicalName +\" \" + getMethodNames[i] + \"() {return this.\" + nextFieldName + \";}\", evalClass));\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\t\n+\t\t\tif (clazz == null && evalClass != null) {\n+\t\t\t\tclazz = evalClass.toClass();\n+\t\t\t}\n+\t\t}\n+\t\t\t\n+\t\tList results = new ArrayList();\n+\n+\t\t// Now populate the newly created objects.\n+\t\tfor(Object nextResult : (List<Object[]>)updateFilterResult) {\n+\t\t\t\n+\t\t\tif (nextResult instanceof Object[]) {\n+\t\t\t\tObject nextObject = clazz.newInstance();\n+\t\t\t\tfor(int i = 0; i < returnAliases.length; i++) {\n+\t\t\t\t\tClass[] formalParams = new Class[] { fieldClasses[i] };\n+\t\t\t\t\tMethod setMethod = clazz.getDeclaredMethod(setMethodNames[i], formalParams);\n+\t\t\t\t\tsetMethod.invoke(nextObject, ((Object[])nextResult)[i]);\n+\t\t\t\t}\n+\t\t\t\t\n+\t\t\t\tresults.add(nextObject);\n+\t\t\t} else\n+\t\t\t\tresults.add(nextResult);\n+\t\t}\n+\t\t\n+\t\treturn results;\n+\t}\n+\t\n+\tpublic IPerceroObject findById(ClassIDPair classIdPair, String userId) {\n+\t\treturn findById(classIdPair, userId, false);\n+\t}\n+\t@SuppressWarnings(\"unchecked\")\n+\tpublic IPerceroObject findById(ClassIDPair classIdPair, String userId, Boolean ignoreCache) {\n+\n+\t\ttry {\n+\t\t\tIPerceroObject result = null;\n+\t\t\tif (!ignoreCache) {\n+\t\t\t\tresult = retrieveFromRedisCache(classIdPair);\n+\t\t\t}\n+\t\n+\t\t\tif (result == null) {\n+\t\n+\t\t\t\tIDataAccessObject<IPerceroObject> dao = (IDataAccessObject<IPerceroObject>) DAORegistry.getInstance().getDataAccessObject(classIdPair.getClassName());\n+\t\t\t\t// Retrieve results BEFORE applying access rules so that our cached value represents the full object.\n+\t\t\t\tresult = dao.retrieveObject(classIdPair, null, false);\n+\t\t\t\tpopulateToManyRelationships(result, true, null);\n+\t\t\t\tpopulateToOneRelationships(result, true, null);\n+\t\n+\t\t\t\t// Now put the object in the cache.\n+\t\t\t\tif (result != null) {\n+\t\t\t\t\tputObjectInRedisCache(result);\n+\t\t\t\t}\n+\t\t\t\telse {\n+\t\t\t\t\t// Not necessarily a problem but could be helpful when debugging.\n+\t\t\t\t\tlog.debug(\"Unable to retrieve object from database: \" + classIdPair.toJson());\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\t// (Re)Set the expiration.\n+\t\t\t\tsetObjectExpiration(result);\n+\t\t\t}\n+\t\t\t\n+\t\t\tresult = cleanObject(result, userId);\n+\t\t\t\n+\t\t\treturn result;\n+\t\t} catch(Exception e) {\n+\t\t\tlog.error(\"Unable to findById: \"+classIdPair.toJson(), e);\n+\t\t}\n+\t\t\n+\t\treturn null;\n+\t}\n+\t\n+\n+\tprivate void putObjectInRedisCache(IPerceroObject perceroObject) {\n+\t\t// Now put the object in the cache.\n+\t\tif (cacheTimeout > 0 && perceroObject != null) {\n+\t\t\tString key = RedisKeyUtils.classIdPair(perceroObject.getClass().getCanonicalName(), perceroObject.getID());\n+\t\t\tcacheDataStore.setValue(key, ((BaseDataObject)perceroObject).toJson());\n+\t\t\tsetObjectExpiration(key);\n+\t\t}\n+\t}\n+\t\n+\tprivate void putObjectsInRedisCache(List<? extends IPerceroObject> results) {\n+\t\tif (cacheTimeout > 0) {\n+\t\t\tMap<String, String> mapJsonObjectStrings = new HashMap<String, String>(results.size());\n+\t\t\tIterator<? extends IPerceroObject> itrDatabaseObjects = results.iterator();\n+\t\t\twhile (itrDatabaseObjects.hasNext()) {\n+\t\t\t\tIPerceroObject nextDatabaseObject = itrDatabaseObjects.next();\n+\t\t\t\tString nextCacheKey = RedisKeyUtils.classIdPair(nextDatabaseObject.getClass().getCanonicalName(), nextDatabaseObject.getID());\n+\t\t\t\t\n+\t\t\t\tmapJsonObjectStrings.put(nextCacheKey, ((BaseDataObject)nextDatabaseObject).toJson());\n+\t\t\t}\n+\t\t\t\n+\t\t\t// Store the objects in redis.\n+\t\t\tcacheDataStore.setValues(mapJsonObjectStrings);\n+\t\t\t// (Re)Set the expiration.\n+\t\t\tcacheDataStore.expire(mapJsonObjectStrings.keySet(), cacheTimeout, TimeUnit.SECONDS);\n+\t\t}\n+\t}\n+\t\n+\tprivate void setObjectExpiration(IPerceroObject perceroObject) {\n+\t\tsetObjectExpiration(RedisKeyUtils.classIdPair(perceroObject.getClass().getCanonicalName(), perceroObject.getID()));\n+\t}\n+\t\n+\tprivate void setObjectExpiration(String key) {\n+\t\t// (Re)Set the expiration.\n+\t\tif (cacheTimeout > 0 && key != null) {\n+\t\t\tcacheDataStore.expire(key, cacheTimeout, TimeUnit.SECONDS);\n+\t\t}\n+\t}\n+\n+\t\n+\t@SuppressWarnings({ \"rawtypes\", \"unchecked\" })\n+\tprivate IPerceroObject retrieveFromRedisCache(ClassIDPair classIdPair) throws Exception {\n+\t\tIPerceroObject result = null;\n+\t\tif (cacheTimeout > 0) {\n+\t\t\tIMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager();\n+\t\t\tClass theClass = MappedClass.forName(classIdPair.getClassName());\n+\t\t\tMappedClass mc = mcm.getMappedClassByClassName(classIdPair.getClassName());\n+\t\t\t\n+\t\t\tString key = RedisKeyUtils.classIdPair(classIdPair.getClassName(), classIdPair.getID());\n+\t\t\tString jsonObjectString = (String) cacheDataStore.getValue(key);\n+\t\t\tif (jsonObjectString != null) {\n+\t\t\t\tif (IJsonObject.class.isAssignableFrom(theClass)) {\n+\t\t\t\t\tIJsonObject jsonObject = (IJsonObject) theClass.newInstance();\n+\t\t\t\t\tjsonObject.fromJson(jsonObjectString);\n+\t\t\t\t\tresult = (IPerceroObject) jsonObject;\n+\t\t\t\t}\n+\t\t\t\telse {\n+\t\t\t\t\tresult = (IPerceroObject) safeObjectMapper.readValue(jsonObjectString, theClass);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\t// Check MappedClass' child classes.\n+\t\t\t\tIterator<MappedClass> itrChildMappedClasses = mc.childMappedClasses.iterator();\n+\t\t\t\twhile (itrChildMappedClasses.hasNext()) {\n+\t\t\t\t\tMappedClass nextChildMc = itrChildMappedClasses.next();\n+\t\t\t\t\tkey = RedisKeyUtils.classIdPair(nextChildMc.className, classIdPair.getID());\n+\t\t\t\t\tjsonObjectString = (String) cacheDataStore.getValue(key);\n+\t\t\t\t\tif (jsonObjectString != null) {\n+\t\t\t\t\t\tresult = (IPerceroObject) safeObjectMapper.readValue(jsonObjectString, theClass);\n+\t\t\t\t\t\treturn result;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\t\n+\t\treturn result;\n+\t}\n+\n+\t@SuppressWarnings({ \"rawtypes\", \"unchecked\" })\n+\tprivate Map<String, IPerceroObject> retrieveFromRedisCache(ClassIDPairs classIdPairs, Boolean pleaseSetTimeout) throws Exception {\n+\t\tMap<String, IPerceroObject> result = new HashMap<String, IPerceroObject>(classIdPairs.getIds().size());\n+\t\t\n+\t\tif (cacheTimeout > 0) {\n+\t\t\tIMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager();\n+\t\t\tClass theClass = MappedClass.forName(classIdPairs.getClassName());\n+\t\t\tMappedClass mc = mcm.getMappedClassByClassName(classIdPairs.getClassName());\n+\t\t\t\n+\t\t\tSet<String> keys = new HashSet<String>(classIdPairs.getIds().size());\n+\t\t\tIterator<String> itrIds = classIdPairs.getIds().iterator();\n+\t\t\twhile (itrIds.hasNext()) {\n+\t\t\t\tString nextId = itrIds.next();\n+\t\t\t\tString nextKey = RedisKeyUtils.classIdPair(classIdPairs.getClassName(), nextId);\n+\t\t\t\tkeys.add(nextKey);\n+\n+\t\t\t\t// Check MappedClass' child classes.\n+\t\t\t\tIterator<MappedClass> itrChildMappedClasses = mc.childMappedClasses.iterator();\n+\t\t\t\twhile (itrChildMappedClasses.hasNext()) {\n+\t\t\t\t\tMappedClass nextChildMc = itrChildMappedClasses.next();\n+\t\t\t\t\tif (nextChildMc.clazz == BaseDataObject.class) {\n+\t\t\t\t\t\t// Reached the top level, so break.\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t}\n+\t\t\t\t\tnextKey = RedisKeyUtils.classIdPair(nextChildMc.className, nextId);\n+\t\t\t\t\tkeys.add(nextKey);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\t\n+//\t\t\tString key = RedisKeyUtils.classIdPair(classIdPair.getClassName(), classIdPair.getID());\n+\t\t\tList<Object> jsonObjectStrings = cacheDataStore.getValues(keys);\n+\t\t\tIterator<Object> itrJsonObjectStrings = jsonObjectStrings.iterator();\n+\t\t\twhile (itrJsonObjectStrings.hasNext()) {\n+\t\t\t\tString jsonObjectString = (String) itrJsonObjectStrings.next();\n+\t\t\t\tif (jsonObjectString != null) {\n+\t\t\t\t\tif (IJsonObject.class.isAssignableFrom(theClass)) {\n+\t\t\t\t\t\tIJsonObject jsonObject = (IJsonObject) theClass.newInstance();\n+\t\t\t\t\t\tjsonObject.fromJson(jsonObjectString);\n+\t\t\t\t\t\tresult.put( (((IPerceroObject) jsonObject).getID()), (IPerceroObject) jsonObject );\n+\t\t\t\t\t}\n+\t\t\t\t\telse {\n+\t\t\t\t\t\tIPerceroObject nextPerceroObject = (IPerceroObject) safeObjectMapper.readValue(jsonObjectString, theClass);\n+\t\t\t\t\t\tresult.put( nextPerceroObject.getID(), nextPerceroObject);\n+\t\t\t\t\t}\n+\t\t\t\t\t\n+\t\t\t\t}\n+//\t\t\t\telse {\n+//\t\t\t\t\t// Check MappedClass' child classes.\n+//\t\t\t\t\tIterator<MappedClass> itrChildMappedClasses = mc.childMappedClasses.iterator();\n+//\t\t\t\t\twhile (itrChildMappedClasses.hasNext()) {\n+//\t\t\t\t\t\tMappedClass nextChildMc = itrChildMappedClasses.next();\n+//\t\t\t\t\t\tkey = RedisKeyUtils.classIdPair(nextChildMc.className, classIdPair.getID());\n+//\t\t\t\t\t\tjsonObjectString = (String) redisDataStore.getValue(key);\n+//\t\t\t\t\t\tif (jsonObjectString != null) {\n+//\t\t\t\t\t\t\tresult = (IPerceroObject) safeObjectMapper.readValue(jsonObjectString, theClass);\n+//\t\t\t\t\t\t\treturn result;\n+//\t\t\t\t\t\t}\n+//\t\t\t\t\t}\n+//\t\t\t\t}\n+\t\t\t}\n+\t\t\t\n+\t\t\tif (pleaseSetTimeout) {\n+\t\t\t\tcacheDataStore.expire(keys, cacheTimeout, TimeUnit.SECONDS);\n+\t\t\t}\n+\t\t}\n+\t\t\n+\t\treturn result;\n+\t}\n+\t\n+\t@SuppressWarnings({ })\n+\tpublic Boolean getReadAccess(ClassIDPair classIdPair, String userId) {\n+\t\tIDataAccessObject<? extends IPerceroObject> dao = DAORegistry.getInstance().getDataAccessObject(classIdPair.getClassName());\n+\t\treturn dao.hasReadAccess(classIdPair, userId);\n+\t}\n+\t\n+\t@SuppressWarnings({ })\n+\tpublic Boolean getDeleteAccess(ClassIDPair classIdPair, String userId) {\n+\t\tIDataAccessObject<? extends IPerceroObject> dao = DAORegistry.getInstance().getDataAccessObject(classIdPair.getClassName());\n+\t\treturn dao.hasDeleteAccess(classIdPair, userId);\n+\t}\n+\t\n+\tpublic List<IPerceroObject> findByIds(ClassIDPairs classIdPairs, String userId) {\n+\t\treturn findByIds(classIdPairs, userId, false);\n+\t}\n+\t@SuppressWarnings({ })\n+\tpublic List<IPerceroObject> findByIds(ClassIDPairs classIdPairs, String userId, Boolean ignoreCache) {\n+\t\tIDataAccessObject<? extends IPerceroObject> dao = DAORegistry.getInstance().getDataAccessObject(classIdPairs.getClassName());\n+\t\tList<IPerceroObject> results = new ArrayList<IPerceroObject>();\n+\t\t\n+\t\ttry {\n+\t\t\t// Copy the ClassIDPairs to find object since we remove any of the\n+\t\t\t// ID's from the list that we find in the cache.\n+\t\t\tClassIDPairs classIdPairsCopy = new ClassIDPairs();\n+\t\t\tclassIdPairsCopy.setClassName(classIdPairs.getClassName());\n+\t\t\tList<String> idsToFind = new ArrayList<String>(classIdPairs.getIds().size());\n+\t\t\tidsToFind.addAll(classIdPairs.getIds());\n+\t\t\tclassIdPairsCopy.setIds(idsToFind);\n+\n+\t\t\tMap<String, IPerceroObject> cachedResults = null;\n+\t\t\tif (!ignoreCache) {\n+\t\t\t\tcachedResults = retrieveFromRedisCache(classIdPairs, true);\n+\t\t\t\tif (cachedResults != null &&!cachedResults.isEmpty()) {\n+\t\t\t\t\t// Add the cached results\n+\t\t\t\t\t\n+\t\t\t\t\tIterator<IPerceroObject> itrCachedResults = cachedResults.values().iterator();\n+\t\t\t\t\twhile (itrCachedResults.hasNext()) {\n+\t\t\t\t\t\tIPerceroObject nextCachedResult = itrCachedResults.next();\n+\t\t\t\t\t\tif (nextCachedResult != null) {\n+\t\t\t\t\t\t\tidsToFind.remove(nextCachedResult.getID());\n+\t\t\t\t\t\t\tresults.add(nextCachedResult);\n+\t\t\t\t\t\t\tsetObjectExpiration(nextCachedResult);\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tList<? extends IPerceroObject> daoObjects = null;\n+\t\t\tif (classIdPairsCopy.getIds() != null && !classIdPairsCopy.getIds().isEmpty()) {\n+\t\t\t\tdaoObjects = dao.retrieveObjects(classIdPairsCopy, userId, false);\n+\t\t\t\t\n+\t\t\t\tfor(IPerceroObject nextResult : daoObjects) {\n+\t\t\t\t\tpopulateToManyRelationships(nextResult, true, null);\n+\t\t\t\t\tpopulateToOneRelationships(nextResult, true, null);\n+\t\t\t\t}\n+\n+\t\t\t\tputObjectsInRedisCache(daoObjects);\t// Only need to put objects in cache that were not already found in cache.\n+\t\t\t\tresults.addAll(daoObjects);\n+\t\t\t}\n+\n+\t\t\t// Now clean the objects for the user.\n+\t\t\tresults = cleanObject(results, userId);\n+\t\t\t\n+\t\t} catch(Exception e) {\n+\t\t\tlog.error(e);\n+\t\t\te.printStackTrace();\n+\t\t}\n+\n+\t\treturn results;\n+\t}\n+\n+\n+\t@SuppressWarnings(\"unchecked\")\n+\tpublic List<IPerceroObject> findByExample(IPerceroObject theQueryObject, List<String> excludeProperties, String userId, Boolean shellOnly) throws SyncException {\n+\t\tIDataAccessObject<IPerceroObject> dao = (IDataAccessObject<IPerceroObject>) DAORegistry.getInstance().getDataAccessObject(theQueryObject.getClass().getCanonicalName());\n+\t\tList<IPerceroObject> results = dao.findByExample(theQueryObject, excludeProperties, userId, shellOnly);\n+\t\t\n+\t\tif (results != null && !results.isEmpty()) {\n+\t\t\tIterator<? extends IPerceroObject> itrResults = results.iterator();\n+\t\t\twhile (itrResults.hasNext()) {\n+\t\t\t\tIPerceroObject nextResult = itrResults.next();\n+\t\t\t\ttry {\n+\t\t\t\t\tpopulateToManyRelationships(nextResult, true, null);\n+\t\t\t\t\tpopulateToOneRelationships(nextResult, true, null);\n+\t\t\t\t} catch (IllegalArgumentException e) {\n+\t\t\t\t\te.printStackTrace();\n+\t\t\t\t\tthrow new SyncDataException(e);\n+\t\t\t\t} catch (IllegalAccessException e) {\n+\t\t\t\t\te.printStackTrace();\n+\t\t\t\t\tthrow new SyncDataException(e);\n+\t\t\t\t} catch (InvocationTargetException e) {\n+\t\t\t\t\te.printStackTrace();\n+\t\t\t\t\tthrow new SyncDataException(e);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\t\n+\t\tputObjectsInRedisCache(results);\n+\t\t\n+\t\t// Now clean the objects for the user.\n+\t\tresults = cleanObject(results, userId);\n+\t\t\n+\t\treturn results;\n+\t}\n+\n+\t@SuppressWarnings({ \"unchecked\", \"rawtypes\" })\n+\tpublic <T extends IPerceroObject> T createObject(T perceroObject, String userId) throws SyncException {\n+\t\t\n+\t\ttry {\n+\t\t\tIDataAccessObject<IPerceroObject> dao = (IDataAccessObject<IPerceroObject>) DAORegistry.getInstance().getDataAccessObject(perceroObject.getClass().getCanonicalName());\n+\t\t\t// Make sure object has an ID.\n+\t\t\tIMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager();\n+\t\t\tMappedClass mappedClass = mcm.getMappedClassByClassName(perceroObject.getClass().getName());\n+\t\t\tif (!mappedClass.hasGeneratedId && !StringUtils.hasText(perceroObject.getID()))\n+\t\t\t\tperceroObject.setID(UUID.randomUUID().toString());\n+\t\t\telse {\n+\t\t\t\t// Check to see if item already exists.\n+\t\t\t\tIPerceroObject existingObject = dao.retrieveObject(BaseDataObject.toClassIdPair(perceroObject), null, false);\n+\t\t\t\tif (existingObject != null)\n+\t\t\t\t{\n+\t\t\t\t\tpopulateToManyRelationships(perceroObject, true, null);\n+\t\t\t\t\tpopulateToOneRelationships(perceroObject, true, null);\n+\t\t\t\t\treturn (T) cleanObject(perceroObject, userId);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\t\n+\t\t\tperceroObject = (T) dao.createObject(perceroObject, userId);\n+\t\t\tif (perceroObject == null) {\n+\t\t\t\treturn perceroObject;\n+\t\t\t}\n+\t\t\tpopulateToManyRelationships(perceroObject, true, null);\n+\t\t\tpopulateToOneRelationships(perceroObject, true, null);\n+\t\t\t\n+\t\t\t// Now update the cache.\n+\t\t\t// TODO: Field-level updates could be REALLY useful here.  Would avoid A TON of UNNECESSARY work...\n+\t\t\tif (cacheTimeout > 0) {\n+\t\t\t\tString key = RedisKeyUtils.classIdPair(perceroObject.getClass().getCanonicalName(), perceroObject.getID());\n+\t\t\t\tcacheDataStore.setValue(key, ((BaseDataObject)perceroObject).toJson());\n+\t\t\t\tcacheDataStore.expire(key, cacheTimeout, TimeUnit.SECONDS);\n+\n+\t\t\t\tSet<String> keysToDelete = new HashSet<String>();\n+\t\t\t\tMappedClass nextMappedClass = mcm.getMappedClassByClassName(perceroObject.getClass().getName());\n+\t\t\t\tIterator<MappedField> itrToManyFields = nextMappedClass.toManyFields.iterator();\n+\t\t\t\twhile(itrToManyFields.hasNext()) {\n+\t\t\t\t\tMappedField nextMappedField = itrToManyFields.next();\n+\t\t\t\t\tObject fieldObject = nextMappedField.getGetter().invoke(perceroObject);\n+\t\t\t\t\tif (fieldObject != null) {\n+\t\t\t\t\t\tif (fieldObject instanceof IPerceroObject) {\n+\t\t\t\t\t\t\tString nextKey = RedisKeyUtils.classIdPair(fieldObject.getClass().getCanonicalName(), ((IPerceroObject)fieldObject).getID());\n+\t\t\t\t\t\t\tkeysToDelete.add(nextKey);\n+\t\t\t\t\t\t}\n+\t\t\t\t\t\telse if (fieldObject instanceof Collection) {\n+\t\t\t\t\t\t\tIterator<Object> itrFieldObject = ((Collection) fieldObject).iterator();\n+\t\t\t\t\t\t\twhile(itrFieldObject.hasNext()) {\n+\t\t\t\t\t\t\t\tObject nextListObject = itrFieldObject.next();\n+\t\t\t\t\t\t\t\tif (nextListObject instanceof IPerceroObject) {\n+\t\t\t\t\t\t\t\t\tString nextKey = RedisKeyUtils.classIdPair(nextListObject.getClass().getCanonicalName(), ((IPerceroObject)nextListObject).getID());\n+\t\t\t\t\t\t\t\t\tkeysToDelete.add(nextKey);\n+\t\t\t\t\t\t\t\t}\n+\t\t\t\t\t\t\t}\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\tIterator<MappedFieldPerceroObject> itrToOneFields = mappedClass.toOneFields.iterator();\n+\t\t\t\twhile(itrToOneFields.hasNext()) {\n+\t\t\t\t\tMappedFieldPerceroObject nextMappedField = itrToOneFields.next();\n+\t\t\t\t\tObject fieldObject = nextMappedField.getGetter().invoke(perceroObject);\n+\t\t\t\t\tif (fieldObject != null) {\n+\t\t\t\t\t\tif (fieldObject instanceof IPerceroObject) {\n+\t\t\t\t\t\t\tString nextKey = RedisKeyUtils.classIdPair(fieldObject.getClass().getCanonicalName(), ((IPerceroObject)fieldObject).getID());\n+\t\t\t\t\t\t\tkeysToDelete.add(nextKey);\n+\t\t\t\t\t\t}\n+\t\t\t\t\t\telse if (fieldObject instanceof Collection) {\n+\t\t\t\t\t\t\tIterator<Object> itrFieldObject = ((Collection) fieldObject).iterator();\n+\t\t\t\t\t\t\twhile(itrFieldObject.hasNext()) {\n+\t\t\t\t\t\t\t\tObject nextListObject = itrFieldObject.next();\n+\t\t\t\t\t\t\t\tif (nextListObject instanceof IPerceroObject) {\n+\t\t\t\t\t\t\t\t\tString nextKey = RedisKeyUtils.classIdPair(nextListObject.getClass().getCanonicalName(), ((IPerceroObject)nextListObject).getID());\n+\t\t\t\t\t\t\t\t\tkeysToDelete.add(nextKey);\n+\t\t\t\t\t\t\t\t}\n+\t\t\t\t\t\t\t}\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\t\n+\t\t\t\tif (!keysToDelete.isEmpty()) {\n+\t\t\t\t\tcacheDataStore.deleteKeys(keysToDelete);\n+\t\t\t\t\t// TODO: Do we simply delete the key?  Or do we refetch the object here and update the key?\n+\t\t\t\t\t//redisDataStore.setValue(nextKey, ((BaseDataObject)perceroObject).toJson());\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\t\n+\t\t\treturn (T) cleanObject(perceroObject, userId);\n+\t\t}\n+\t\tcatch(PropertyValueException pve) {\n+\t\t\tlog.error(\"Error creating object\", pve);\n+\t\t\t\n+\t\t\tSyncDataException sde = new SyncDataException(SyncDataException.MISSING_REQUIRED_FIELD, SyncDataException.MISSING_REQUIRED_FIELD_CODE, \"Missing required field \" + pve.getPropertyName());\n+\t\t\tsde.fieldName = pve.getPropertyName();\n+\t\t\tthrow sde;\n+\t\t}\n+\t\tcatch(Exception e) {\n+\t\t\tlog.error(\"Error creating object\", e);\n+\t\t\t\n+\t\t\tSyncDataException sde = new SyncDataException(SyncDataException.CREATE_OBJECT_ERROR, SyncDataException.CREATE_OBJECT_ERROR_CODE);\n+\t\t\tthrow sde;\n+\t\t}\n+\t}\n+\t\n+\n+\t////////////////////////////////////////////////////\n+\t//\tPUT\n+\t////////////////////////////////////////////////////\n+\t@SuppressWarnings(\"unchecked\")\n+\tpublic <T extends IPerceroObject> T putObject(T perceroObject, Map<ClassIDPair, Collection<MappedField>> changedFields, String userId) throws SyncException {","path":"src/main/java/com/percero/agents/sync/services/DAODataProvider.java","position":649,"original_position":649,"commit_id":"87fa556d144bcd1b429808e44fbcc06220ac8d2d","original_commit_id":"87fa556d144bcd1b429808e44fbcc06220ac8d2d","user":{"login":"curb47","id":382260,"avatar_url":"https://avatars.githubusercontent.com/u/382260?v=3","gravatar_id":"","url":"https://api.github.com/users/curb47","html_url":"https://github.com/curb47","followers_url":"https://api.github.com/users/curb47/followers","following_url":"https://api.github.com/users/curb47/following{/other_user}","gists_url":"https://api.github.com/users/curb47/gists{/gist_id}","starred_url":"https://api.github.com/users/curb47/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/curb47/subscriptions","organizations_url":"https://api.github.com/users/curb47/orgs","repos_url":"https://api.github.com/users/curb47/repos","events_url":"https://api.github.com/users/curb47/events{/privacy}","received_events_url":"https://api.github.com/users/curb47/received_events","type":"User","site_admin":false},"body":"Agreed.  May or may not happen right now.","created_at":"2015-09-24T20:36:27Z","updated_at":"2015-09-24T20:36:27Z","html_url":"https://github.com/ActiveStack/syncengine/pull/7#discussion_r40369473","pull_request_url":"https://api.github.com/repos/ActiveStack/syncengine/pulls/7","_links":{"self":{"href":"https://api.github.com/repos/ActiveStack/syncengine/pulls/comments/40369473"},"html":{"href":"https://github.com/ActiveStack/syncengine/pull/7#discussion_r40369473"},"pull_request":{"href":"https://api.github.com/repos/ActiveStack/syncengine/pulls/7"}}},"pull_request":{"url":"https://api.github.com/repos/ActiveStack/syncengine/pulls/7","id":45350761,"html_url":"https://github.com/ActiveStack/syncengine/pull/7","diff_url":"https://github.com/ActiveStack/syncengine/pull/7.diff","patch_url":"https://github.com/ActiveStack/syncengine/pull/7.patch","issue_url":"https://api.github.com/repos/ActiveStack/syncengine/issues/7","number":7,"state":"open","locked":false,"title":"DAO and UpdateTable","user":{"login":"curb47","id":382260,"avatar_url":"https://avatars.githubusercontent.com/u/382260?v=3","gravatar_id":"","url":"https://api.github.com/users/curb47","html_url":"https://github.com/curb47","followers_url":"https://api.github.com/users/curb47/followers","following_url":"https://api.github.com/users/curb47/following{/other_user}","gists_url":"https://api.github.com/users/curb47/gists{/gist_id}","starred_url":"https://api.github.com/users/curb47/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/curb47/subscriptions","organizations_url":"https://api.github.com/users/curb47/orgs","repos_url":"https://api.github.com/users/curb47/repos","events_url":"https://api.github.com/users/curb47/events{/privacy}","received_events_url":"https://api.github.com/users/curb47/received_events","type":"User","site_admin":false},"body":"Added DAODataProvider and UpdateTable functionality","created_at":"2015-09-17T22:39:10Z","updated_at":"2015-09-24T20:36:27Z","closed_at":null,"merged_at":null,"merge_commit_sha":"0cf6eba38316f4845cf5a95a1bd81935f270d1c2","assignee":{"login":"curb47","id":382260,"avatar_url":"https://avatars.githubusercontent.com/u/382260?v=3","gravatar_id":"","url":"https://api.github.com/users/curb47","html_url":"https://github.com/curb47","followers_url":"https://api.github.com/users/curb47/followers","following_url":"https://api.github.com/users/curb47/following{/other_user}","gists_url":"https://api.github.com/users/curb47/gists{/gist_id}","starred_url":"https://api.github.com/users/curb47/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/curb47/subscriptions","organizations_url":"https://api.github.com/users/curb47/orgs","repos_url":"https://api.github.com/users/curb47/repos","events_url":"https://api.github.com/users/curb47/events{/privacy}","received_events_url":"https://api.github.com/users/curb47/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/ActiveStack/syncengine/pulls/7/commits","review_comments_url":"https://api.github.com/repos/ActiveStack/syncengine/pulls/7/comments","review_comment_url":"https://api.github.com/repos/ActiveStack/syncengine/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ActiveStack/syncengine/issues/7/comments","statuses_url":"https://api.github.com/repos/ActiveStack/syncengine/statuses/87fa556d144bcd1b429808e44fbcc06220ac8d2d","head":{"label":"ActiveStack:dao","ref":"dao","sha":"87fa556d144bcd1b429808e44fbcc06220ac8d2d","user":{"login":"ActiveStack","id":13102255,"avatar_url":"https://avatars.githubusercontent.com/u/13102255?v=3","gravatar_id":"","url":"https://api.github.com/users/ActiveStack","html_url":"https://github.com/ActiveStack","followers_url":"https://api.github.com/users/ActiveStack/followers","following_url":"https://api.github.com/users/ActiveStack/following{/other_user}","gists_url":"https://api.github.com/users/ActiveStack/gists{/gist_id}","starred_url":"https://api.github.com/users/ActiveStack/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ActiveStack/subscriptions","organizations_url":"https://api.github.com/users/ActiveStack/orgs","repos_url":"https://api.github.com/users/ActiveStack/repos","events_url":"https://api.github.com/users/ActiveStack/events{/privacy}","received_events_url":"https://api.github.com/users/ActiveStack/received_events","type":"Organization","site_admin":false},"repo":{"id":40547910,"name":"syncengine","full_name":"ActiveStack/syncengine","owner":{"login":"ActiveStack","id":13102255,"avatar_url":"https://avatars.githubusercontent.com/u/13102255?v=3","gravatar_id":"","url":"https://api.github.com/users/ActiveStack","html_url":"https://github.com/ActiveStack","followers_url":"https://api.github.com/users/ActiveStack/followers","following_url":"https://api.github.com/users/ActiveStack/following{/other_user}","gists_url":"https://api.github.com/users/ActiveStack/gists{/gist_id}","starred_url":"https://api.github.com/users/ActiveStack/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ActiveStack/subscriptions","organizations_url":"https://api.github.com/users/ActiveStack/orgs","repos_url":"https://api.github.com/users/ActiveStack/repos","events_url":"https://api.github.com/users/ActiveStack/events{/privacy}","received_events_url":"https://api.github.com/users/ActiveStack/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ActiveStack/syncengine","description":"","fork":false,"url":"https://api.github.com/repos/ActiveStack/syncengine","forks_url":"https://api.github.com/repos/ActiveStack/syncengine/forks","keys_url":"https://api.github.com/repos/ActiveStack/syncengine/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ActiveStack/syncengine/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ActiveStack/syncengine/teams","hooks_url":"https://api.github.com/repos/ActiveStack/syncengine/hooks","issue_events_url":"https://api.github.com/repos/ActiveStack/syncengine/issues/events{/number}","events_url":"https://api.github.com/repos/ActiveStack/syncengine/events","assignees_url":"https://api.github.com/repos/ActiveStack/syncengine/assignees{/user}","branches_url":"https://api.github.com/repos/ActiveStack/syncengine/branches{/branch}","tags_url":"https://api.github.com/repos/ActiveStack/syncengine/tags","blobs_url":"https://api.github.com/repos/ActiveStack/syncengine/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ActiveStack/syncengine/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ActiveStack/syncengine/git/refs{/sha}","trees_url":"https://api.github.com/repos/ActiveStack/syncengine/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ActiveStack/syncengine/statuses/{sha}","languages_url":"https://api.github.com/repos/ActiveStack/syncengine/languages","stargazers_url":"https://api.github.com/repos/ActiveStack/syncengine/stargazers","contributors_url":"https://api.github.com/repos/ActiveStack/syncengine/contributors","subscribers_url":"https://api.github.com/repos/ActiveStack/syncengine/subscribers","subscription_url":"https://api.github.com/repos/ActiveStack/syncengine/subscription","commits_url":"https://api.github.com/repos/ActiveStack/syncengine/commits{/sha}","git_commits_url":"https://api.github.com/repos/ActiveStack/syncengine/git/commits{/sha}","comments_url":"https://api.github.com/repos/ActiveStack/syncengine/comments{/number}","issue_comment_url":"https://api.github.com/repos/ActiveStack/syncengine/issues/comments{/number}","contents_url":"https://api.github.com/repos/ActiveStack/syncengine/contents/{+path}","compare_url":"https://api.github.com/repos/ActiveStack/syncengine/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ActiveStack/syncengine/merges","archive_url":"https://api.github.com/repos/ActiveStack/syncengine/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ActiveStack/syncengine/downloads","issues_url":"https://api.github.com/repos/ActiveStack/syncengine/issues{/number}","pulls_url":"https://api.github.com/repos/ActiveStack/syncengine/pulls{/number}","milestones_url":"https://api.github.com/repos/ActiveStack/syncengine/milestones{/number}","notifications_url":"https://api.github.com/repos/ActiveStack/syncengine/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ActiveStack/syncengine/labels{/name}","releases_url":"https://api.github.com/repos/ActiveStack/syncengine/releases{/id}","created_at":"2015-08-11T14:53:43Z","updated_at":"2015-08-11T14:59:59Z","pushed_at":"2015-09-23T07:42:38Z","git_url":"git://github.com/ActiveStack/syncengine.git","ssh_url":"git@github.com:ActiveStack/syncengine.git","clone_url":"https://github.com/ActiveStack/syncengine.git","svn_url":"https://github.com/ActiveStack/syncengine","homepage":null,"size":679,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":3,"forks":1,"open_issues":3,"watchers":0,"default_branch":"master"}},"base":{"label":"ActiveStack:master","ref":"master","sha":"7367aa857294cb8d4159d5099aeba8b2420eb358","user":{"login":"ActiveStack","id":13102255,"avatar_url":"https://avatars.githubusercontent.com/u/13102255?v=3","gravatar_id":"","url":"https://api.github.com/users/ActiveStack","html_url":"https://github.com/ActiveStack","followers_url":"https://api.github.com/users/ActiveStack/followers","following_url":"https://api.github.com/users/ActiveStack/following{/other_user}","gists_url":"https://api.github.com/users/ActiveStack/gists{/gist_id}","starred_url":"https://api.github.com/users/ActiveStack/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ActiveStack/subscriptions","organizations_url":"https://api.github.com/users/ActiveStack/orgs","repos_url":"https://api.github.com/users/ActiveStack/repos","events_url":"https://api.github.com/users/ActiveStack/events{/privacy}","received_events_url":"https://api.github.com/users/ActiveStack/received_events","type":"Organization","site_admin":false},"repo":{"id":40547910,"name":"syncengine","full_name":"ActiveStack/syncengine","owner":{"login":"ActiveStack","id":13102255,"avatar_url":"https://avatars.githubusercontent.com/u/13102255?v=3","gravatar_id":"","url":"https://api.github.com/users/ActiveStack","html_url":"https://github.com/ActiveStack","followers_url":"https://api.github.com/users/ActiveStack/followers","following_url":"https://api.github.com/users/ActiveStack/following{/other_user}","gists_url":"https://api.github.com/users/ActiveStack/gists{/gist_id}","starred_url":"https://api.github.com/users/ActiveStack/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ActiveStack/subscriptions","organizations_url":"https://api.github.com/users/ActiveStack/orgs","repos_url":"https://api.github.com/users/ActiveStack/repos","events_url":"https://api.github.com/users/ActiveStack/events{/privacy}","received_events_url":"https://api.github.com/users/ActiveStack/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ActiveStack/syncengine","description":"","fork":false,"url":"https://api.github.com/repos/ActiveStack/syncengine","forks_url":"https://api.github.com/repos/ActiveStack/syncengine/forks","keys_url":"https://api.github.com/repos/ActiveStack/syncengine/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ActiveStack/syncengine/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ActiveStack/syncengine/teams","hooks_url":"https://api.github.com/repos/ActiveStack/syncengine/hooks","issue_events_url":"https://api.github.com/repos/ActiveStack/syncengine/issues/events{/number}","events_url":"https://api.github.com/repos/ActiveStack/syncengine/events","assignees_url":"https://api.github.com/repos/ActiveStack/syncengine/assignees{/user}","branches_url":"https://api.github.com/repos/ActiveStack/syncengine/branches{/branch}","tags_url":"https://api.github.com/repos/ActiveStack/syncengine/tags","blobs_url":"https://api.github.com/repos/ActiveStack/syncengine/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ActiveStack/syncengine/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ActiveStack/syncengine/git/refs{/sha}","trees_url":"https://api.github.com/repos/ActiveStack/syncengine/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ActiveStack/syncengine/statuses/{sha}","languages_url":"https://api.github.com/repos/ActiveStack/syncengine/languages","stargazers_url":"https://api.github.com/repos/ActiveStack/syncengine/stargazers","contributors_url":"https://api.github.com/repos/ActiveStack/syncengine/contributors","subscribers_url":"https://api.github.com/repos/ActiveStack/syncengine/subscribers","subscription_url":"https://api.github.com/repos/ActiveStack/syncengine/subscription","commits_url":"https://api.github.com/repos/ActiveStack/syncengine/commits{/sha}","git_commits_url":"https://api.github.com/repos/ActiveStack/syncengine/git/commits{/sha}","comments_url":"https://api.github.com/repos/ActiveStack/syncengine/comments{/number}","issue_comment_url":"https://api.github.com/repos/ActiveStack/syncengine/issues/comments{/number}","contents_url":"https://api.github.com/repos/ActiveStack/syncengine/contents/{+path}","compare_url":"https://api.github.com/repos/ActiveStack/syncengine/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ActiveStack/syncengine/merges","archive_url":"https://api.github.com/repos/ActiveStack/syncengine/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ActiveStack/syncengine/downloads","issues_url":"https://api.github.com/repos/ActiveStack/syncengine/issues{/number}","pulls_url":"https://api.github.com/repos/ActiveStack/syncengine/pulls{/number}","milestones_url":"https://api.github.com/repos/ActiveStack/syncengine/milestones{/number}","notifications_url":"https://api.github.com/repos/ActiveStack/syncengine/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ActiveStack/syncengine/labels{/name}","releases_url":"https://api.github.com/repos/ActiveStack/syncengine/releases{/id}","created_at":"2015-08-11T14:53:43Z","updated_at":"2015-08-11T14:59:59Z","pushed_at":"2015-09-23T07:42:38Z","git_url":"git://github.com/ActiveStack/syncengine.git","ssh_url":"git@github.com:ActiveStack/syncengine.git","clone_url":"https://github.com/ActiveStack/syncengine.git","svn_url":"https://github.com/ActiveStack/syncengine","homepage":null,"size":679,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":3,"forks":1,"open_issues":3,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ActiveStack/syncengine/pulls/7"},"html":{"href":"https://github.com/ActiveStack/syncengine/pull/7"},"issue":{"href":"https://api.github.com/repos/ActiveStack/syncengine/issues/7"},"comments":{"href":"https://api.github.com/repos/ActiveStack/syncengine/issues/7/comments"},"review_comments":{"href":"https://api.github.com/repos/ActiveStack/syncengine/pulls/7/comments"},"review_comment":{"href":"https://api.github.com/repos/ActiveStack/syncengine/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ActiveStack/syncengine/pulls/7/commits"},"statuses":{"href":"https://api.github.com/repos/ActiveStack/syncengine/statuses/87fa556d144bcd1b429808e44fbcc06220ac8d2d"}}}}