Java tutorial
/****************************************************************************** * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for * the specific language governing rights and limitations under the License. * * The Original Code is: Jsoda * The Initial Developer of the Original Code is: William Wong (williamw520@gmail.com) * Portions created by William Wong are Copyright (C) 2012 William Wong, All Rights Reserved. * ******************************************************************************/ package wwutil.jsoda; import java.io.*; import java.net.*; import java.util.*; import java.lang.annotation.*; import java.lang.reflect.*; import java.util.concurrent.*; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.lang.StringUtils; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.services.s3.AmazonS3Client; import wwutil.sys.FnUtil; import wwutil.sys.FnUtil.*; import wwutil.sys.ReflectUtil; import wwutil.model.MemCacheable; import wwutil.model.MemCacheableSimple; import wwutil.model.AnnotationRegistry; import wwutil.model.AnnotationClassHandler; import wwutil.model.AnnotationFieldHandler; import wwutil.model.ValidationException; import wwutil.model.BuiltinFunc; import wwutil.model.annotation.Key; import wwutil.model.annotation.Transient; import wwutil.model.annotation.PrePersist; import wwutil.model.annotation.PreValidation; import wwutil.model.annotation.PostLoad; import wwutil.model.annotation.DbType; import wwutil.model.annotation.Model; import wwutil.model.annotation.AttrName; import wwutil.model.annotation.CachePolicy; import wwutil.model.annotation.CacheByField; import wwutil.model.annotation.VersionLocking; import wwutil.model.annotation.S3Field; /** * Simple object-db mapping service for AWS. Make storing POJO in SimpleDB/DynamoDB easy. * Class is thread-safe. * * See utest.JsodaTest for usage. */ public class Jsoda { private static Log log = LogFactory.getLog(Jsoda.class); // Services private AWSCredentials credentials; private ObjCacheMgr objCacheMgr; private SimpleDBService sdbMgr; private DynamoDBService ddbMgr; private AmazonS3Client s3Client; private AnnotationRegistry preStore1Registry; private AnnotationRegistry preStore2Registry; private AnnotationRegistry postLoadRegistry; private AnnotationRegistry validationRegistry; private String globalPrefix; private String defaultS3Bucket = ""; private String s3KeyPrefix = ""; private String s3EndPoint; // Model registry private Map<String, Class> modelClasses = new ConcurrentHashMap<String, Class>(); private Map<String, DbService> modelDb = new ConcurrentHashMap<String, DbService>(); private Map<String, String> modelTables = new ConcurrentHashMap<String, String>(); private Map<String, Field> modelIdFields = new ConcurrentHashMap<String, Field>(); private Map<String, Field> modelRangeFields = new ConcurrentHashMap<String, Field>(); private Map<String, Field> modelVersionFields = new ConcurrentHashMap<String, Field>(); private Map<String, Integer> modelCachePolicy = new ConcurrentHashMap<String, Integer>(); // -1 for non-cacheable private Map<String, Map<String, Field>> modelAllFieldMap = new ConcurrentHashMap<String, Map<String, Field>>(); // all fields include db, S3, and transient private Map<String, Map<String, Field>> modelDbFieldMap = new ConcurrentHashMap<String, Map<String, Field>>(); // db fields are the ones stored at SimpleDB/DynamoDB private Map<String, Map<String, Field>> modelAttrFieldMap = new ConcurrentHashMap<String, Map<String, Field>>(); // maps db attr names to db field names private Map<String, Map<String, String>> modelFieldAttrMap = new ConcurrentHashMap<String, Map<String, String>>(); // mape db field names to attr names. private Map<String, Map<String, Field>> modelS3FieldMap = new ConcurrentHashMap<String, Map<String, Field>>(); // s3 fields are the ones stored at S3 private Map<String, Set<String>> modelCacheByFields = new ConcurrentHashMap<String, Set<String>>(); private Map<String, Method> modelPrePersistMethod = new ConcurrentHashMap<String, Method>(); private Map<String, Method> modelPreValidationMethod = new ConcurrentHashMap<String, Method>(); private Map<String, Method> modelPostLoadMethod = new ConcurrentHashMap<String, Method>(); private Map<String, Dao> modelDao = new ConcurrentHashMap<String, Dao>(); private Map<String, S3Dao> modelS3Dao = new ConcurrentHashMap<String, S3Dao>(); private Map<String, EUtil> modelEUtil = new ConcurrentHashMap<String, EUtil>(); /** Create a Jsoda object with the AWS Access Key ID and Secret Access Key * The tables in the database scope belonged to the AWS Access Key ID will be accessed. */ public Jsoda(AWSCredentials cred) throws Exception { this(cred, new MemCacheableSimple(10000)); } /** Set a cache service for the Jsoda object. All objects accessed via the Jsoda object will be cached according to their CachePolicy. */ public Jsoda(AWSCredentials cred, MemCacheable memCacheable) throws Exception { if (cred == null || cred.getAWSAccessKeyId() == null || cred.getAWSSecretKey() == null) throw new IllegalArgumentException("AWS credentials are needed."); this.credentials = cred; this.objCacheMgr = new ObjCacheMgr(this, memCacheable); this.sdbMgr = new SimpleDBService(this, cred); this.ddbMgr = new DynamoDBService(this, cred); this.s3Client = new AmazonS3Client(cred); this.preStore1Registry = BuiltinFunc.clonePreStore1Registry(); this.preStore2Registry = BuiltinFunc.clonePreStore2Registry(); this.validationRegistry = BuiltinFunc.cloneValidationRegistry(); this.postLoadRegistry = BuiltinFunc.clonePostLoadRegistry(); } /** Set a new cache service for this Jsoda object. All old cached content are gone. */ public void setMemCacheable(MemCacheable memCacheable) { objCacheMgr.setMemCacheable(memCacheable); } /** Return the cache service object. * Should only use the returned MemCacheable for dumping caching statistics and nothing else. */ public MemCacheable getMemCacheable() { return objCacheMgr.getMemCacheable(); } /** Set the AWS service endpoint for the underlying dbtype. Different AWS region might have different endpoint. */ public Jsoda setDbEndpoint(DbType dbtype, String endpoint) { getDbService(dbtype).setDbEndpoint(endpoint); return this; } /** Get the AWS service endpoint for the underlying dbtype. Return null for using AWS default. */ public String getDbEndpoint(DbType dbtype) { return getDbService(dbtype).getDbEndpoint(); } /** Set the AWS service endpoint for S3. Different AWS region might have different endpoint. */ public Jsoda setS3Endpoint(String endpoint) { this.s3EndPoint = endpoint; s3Client.setEndpoint(endpoint); return this; } /** Get the AWS service endpoint for S3. Return null for using AWS default. */ public String getS3Endpoint() { return this.s3EndPoint; } /** Set the global table prefix to add a prefix to all tables managed by the Jsoda object. * The global prefix is added in front of any other per-model-class prefix defined in @Model.prefix. * Global prefix is useful to scope all the tables for different purposes, e.g. creating test tables. */ public Jsoda setGlobalPrefix(String globalPrefix) { this.globalPrefix = globalPrefix; return this; } /** Return the global table prefix to add a prefix to all tables managed by the Jsoda object. */ public String getGlobalPrefix() { return globalPrefix; } /** Set default S3Bucket. This is used when @S3Field.s3Bucket is not set. */ public Jsoda setDefaultS3Bucket(String defaultS3Bucket) { this.defaultS3Bucket = defaultS3Bucket; return this; } public String getDefaultS3Bucket() { return this.defaultS3Bucket; } /** Set the global s3 key prefix to add a prefix to all S3 key managed by the Jsoda object. * The global prefix is added in front of any other per-model-field prefix defined in @S3Field. * Global prefix is useful to scope all the S3 objects for different purposes, e.g. creating test objects. */ public Jsoda setS3KeyPrefix(String s3KeyPrefix) { this.s3KeyPrefix = s3KeyPrefix; return this; } public String getS3KeyPrefix() { return this.s3KeyPrefix; } /** Shut down any underlying database services and free up resources */ public void shutdown() { objCacheMgr.shutdown(); sdbMgr.shutdown(); ddbMgr.shutdown(); modelClasses.clear(); modelTables.clear(); modelDb.clear(); modelTables.clear(); modelIdFields.clear(); modelRangeFields.clear(); modelVersionFields.clear(); modelCachePolicy.clear(); modelAllFieldMap.clear(); modelDbFieldMap.clear(); modelAttrFieldMap.clear(); modelFieldAttrMap.clear(); modelS3FieldMap.clear(); modelCacheByFields.clear(); modelPrePersistMethod.clear(); modelPreValidationMethod.clear(); modelPostLoadMethod.clear(); modelDao.clear(); modelS3Dao.clear(); modelEUtil.clear(); } /** Register a POJO model class. Calling again will re-register the model class, replacing the old one. */ public <T> void registerModel(Class<T> modelClass) throws JsodaException { registerModel(modelClass, null); } /** Register a POJO model class. Register the model to use the dbtype, ignoring the model's dbtype annotation. */ public <T> void registerModel(Class<T> modelClass, DbType dbtype) throws JsodaException { try { String modelName = getModelName(modelClass); List<Field> allFields = ReflectUtil.getAllFields(modelClass); Field idField = toKeyField(modelClass, allFields, false); Field rangeField = toKeyField(modelClass, allFields, true); List<Field> savedFields = FnUtil.filter(allFields, new PredicateFn<Field>() { public boolean apply(Field f) { return Modifier.isTransient(f.getModifiers()) || // skip transient field ReflectUtil.hasAnnotation(f, Transient.class); } }); List<Field> dbFields = FnUtil.filter(savedFields, new PredicateFn<Field>() { public boolean apply(Field f) { return ReflectUtil.hasAnnotation(f, S3Field.class); // skip S3 field } }); List<Field> s3Fields = FnUtil.filter(savedFields, new PredicateFn<Field>() { public boolean apply(Field f) { return !ReflectUtil.hasAnnotation(f, S3Field.class); // skip non-S3 field } }); validateClass(modelClass); validateFields(modelClass, idField, rangeField); modelClasses.put(modelName, modelClass); modelDb.put(modelName, toDbService(modelClass, dbtype)); modelTables.put(modelName, toTableName(modelClass)); modelIdFields.put(modelName, idField); if (rangeField != null) modelRangeFields.put(modelName, rangeField); Field versionField = ReflectUtil.findAnnotatedField(modelClass, VersionLocking.class); if (versionField != null) modelVersionFields.put(modelName, versionField); toCachePolicy(modelName, modelClass); modelAllFieldMap.put(modelName, toFieldMap(allFields)); modelDbFieldMap.put(modelName, toFieldMap(dbFields)); modelAttrFieldMap.put(modelName, toAttrFieldMap(dbFields)); modelFieldAttrMap.put(modelName, toFieldAttrMap(dbFields)); modelS3FieldMap.put(modelName, toFieldMap(s3Fields)); modelCacheByFields.put(modelName, toCacheByFields(dbFields)); // Build CacheByFields on all db fields, including the Id field toAnnotatedMethods(modelName, modelClass); modelDao.put(modelName, new Dao<T>(modelClass, this)); modelS3Dao.put(modelName, new S3Dao<T>(modelClass, this)); modelEUtil.put(modelName, new EUtil<T>(modelClass, this)); preStore1Registry.checkModelOnFields(modelAllFieldMap.get(modelName)); preStore2Registry.checkModelOnFields(modelAllFieldMap.get(modelName)); validationRegistry.checkModelOnFields(modelAllFieldMap.get(modelName)); postLoadRegistry.checkModelOnFields(modelAllFieldMap.get(modelName)); } catch (JsodaException je) { throw je; } catch (Exception e) { throw new JsodaException("Failed to register model class", e); } } public boolean isRegistered(Class modelClass) { return (modelClasses.get(getModelName(modelClass)) != null); } public void registerPreStore1Handler(Class annotationClass, AnnotationFieldHandler handler) { preStore1Registry.register(annotationClass, handler); } public void registerPreStore2Handler(Class annotationClass, AnnotationFieldHandler handler) { preStore2Registry.register(annotationClass, handler); } public void registerValidationHandler(Class annotationClass, AnnotationFieldHandler handler) { validationRegistry.register(annotationClass, handler); } public void registerPostLoadHandler(Class annotationClass, AnnotationFieldHandler handler) { postLoadRegistry.register(annotationClass, handler); } void validateRegisteredModel(Class modelClass) throws IllegalArgumentException { if (!modelClasses.containsKey(getModelName(modelClass))) throw new IllegalArgumentException("Model class " + modelClass + " has not been registered."); } void validateRegisteredModel(String modelName) throws IllegalArgumentException { if (!modelClasses.containsKey(modelName)) throw new IllegalArgumentException("Model class " + modelName + " has not been registered."); } void validateField(String modelName, String field) { if (getField(modelName, field) == null) throw new IllegalArgumentException("Field " + field + " does not exist in " + modelName); } /** Return the model name of a model class. */ public static String getModelName(Class modelClass) { int index; String name = modelClass.getName(); index = name.lastIndexOf("."); name = (index == -1 ? name : name.substring(index + 1)); // Get classname in pkg.classname index = name.lastIndexOf("$"); name = (index == -1 ? name : name.substring(index + 1)); // Get nested_classname in pkg.class1$nested_classname return name; } /** Return the registered model class by its name. */ public Class getModelClass(String modelName) { validateRegisteredModel(modelName); return modelClasses.get(modelName); } /** Return the DbService for the registered model class. */ DbService getDb(String modelName) { validateRegisteredModel(modelName); return modelDb.get(modelName); } public AmazonS3Client getS3Client() { return s3Client; } /** Return the table name of a registered model class. */ String getModelTable(String modelName) { validateRegisteredModel(modelName); return modelTables.get(modelName); } /** Return the table name of a registered model class. */ String getModelTable(Class modelClass) { return getModelTable((String) getModelName(modelClass)); } /** Return a field of a registered model class by the field name, including all fields. */ Field getField(String modelName, String fieldName) { validateRegisteredModel(modelName); return modelAllFieldMap.get(modelName).get(fieldName); } /** Return the Id field of a registered model class. */ Field getIdField(String modelName) { validateRegisteredModel(modelName); return modelIdFields.get(modelName); } /** Return the RangeKey field of a registered model class. */ Field getRangeField(String modelName) { validateRegisteredModel(modelName); return modelRangeFields.get(modelName); } /** Return the VersionLocking field of a registered model class. */ Field getVersionField(String modelName) { validateRegisteredModel(modelName); return modelVersionFields.get(modelName); } /** Check to see if field is the Id field. */ boolean isIdField(String modelName, String fieldName) { return getIdField(modelName).getName().equals(fieldName); } /** Check to see if field is an RangeKey field. */ boolean isRangeField(String modelName, String fieldName) { Field rangeField = getRangeField(modelName); return rangeField != null && rangeField.getName().equals(fieldName); } Map<String, Field> getAllFieldMap(String modelName) { return modelAllFieldMap.get(modelName); } // DB Table API /** Create modelClass' table in the registered model's database */ public <T> void createModelTable(Class<T> modelClass) throws JsodaException { if (!isRegistered(modelClass)) registerModel(modelClass); String modelName = getModelName(modelClass); getDb(modelName).createModelTable(modelName); } /** Delete modelClass' table in the registered model's database */ public <T> void deleteModelTable(Class<T> modelClass) throws JsodaException { if (!isRegistered(modelClass)) registerModel(modelClass); String modelName = getModelName(modelClass); String tableName = getModelTable(modelName); getDb(modelName).deleteTable(tableName); } /** Delete the table in the database, as named in tableName, in the dbtype */ public void deleteNativeTable(DbType dbtype, String tableName) { getDbService(dbtype).deleteTable(tableName); } /** Create tables for all registered models. */ @SuppressWarnings("unchecked") public void createRegisteredTables() throws JsodaException { for (Class modelClass : modelClasses.values()) { createModelTable(modelClass); } } /** Get the list of native table names from the underlying database. * @param dbtype the dbtype, as defined in Model. */ public List<String> listNativeTables(DbType dbtype) { return getDbService(dbtype).listTables(); } /** Get the data access object for the model class. * DAO has methods to load or save the data object. * The modelClass is registered automatically if it has not been registered. * <pre> * e.g. * Dao<Model1> dao1 = jsoda.dao(Model1.class); * Dao<Model2> dao2 = jsoda.dao(Model2.class); * dao1.put(model1One); * dao1.put(model1Two); * dao2.put(model2One); * dao2.put(model2Two); * </pre> */ @SuppressWarnings("unchecked") public <T> Dao<T> dao(Class<T> modelClass) throws JsodaException { if (!isRegistered(modelClass)) registerModel(modelClass); return (Dao<T>) modelDao.get(getModelName(modelClass)); } /** Create a Query object for a model class. Additional conditions can be specified on the query. * Call this method or the Dao's constructor to create a dao for a model class. * <pre> * e.g. * Dao<Model1> query1 = jsoda.query(Model1.class); * Dao<Model2> query2 = new Query<Model2>(Model2.class, jsoda); * </pre> */ public <T> Query<T> query(Class<T> modelClass) throws JsodaException { if (!isRegistered(modelClass)) registerModel(modelClass); return new Query<T>(modelClass, this); } @SuppressWarnings("unchecked") public <T> S3Dao<T> s3dao(Class<T> modelClass) throws JsodaException { if (!isRegistered(modelClass)) registerModel(modelClass); return (S3Dao<T>) modelS3Dao.get(getModelName(modelClass)); } /** Get the helper entity util class for util methods to work on the instance object. * <pre> * e.g. * EUtil<Model1> model1 = jsoda.eutil(Model1.class); * EUtil<Model2> model2 = jsoda.eutil(Model2.class); * </pre> */ @SuppressWarnings("unchecked") public <T> EUtil<T> eutil(Class<T> modelClass) throws JsodaException { if (!isRegistered(modelClass)) registerModel(modelClass); return (EUtil<T>) modelEUtil.get(getModelName(modelClass)); } /** @deprecated Use EUtil.dump() instead. */ public static String dump(Object obj) { return EUtil.dump(obj); } // Package level methods ObjCacheMgr getObjCacheMgr() { return objCacheMgr; } Field getFieldByAttr(String modelName, String attrName) { validateRegisteredModel(modelName); Field idField = modelIdFields.get(modelName); Field field = modelAttrFieldMap.get(modelName).get(attrName); if (field == null) { if (idField.getName().equals(attrName)) return idField; else return null; } return field; } Map<String, String> getFieldAttrMap(String modelName) { return modelFieldAttrMap.get(modelName); } Map<String, Field> getAttrFieldMap(String modelName) { return modelAttrFieldMap.get(modelName); } Map<String, Field> getS3Fields(String modelName) { return modelS3FieldMap.get(modelName); } Set<String> getCacheByFields(String modelName) { return modelCacheByFields.get(modelName); } Integer getCachePolicy(String modelName) { return modelCachePolicy.get(modelName); } Method getPrePersistMethod(String modelName) { return modelPrePersistMethod.get(modelName); } Method getPreValidationMethod(String modelName) { return modelPreValidationMethod.get(modelName); } Method getPostLoadMethod(String modelName) { return modelPostLoadMethod.get(modelName); } String makePkKey(String modelName, Object dataObj) throws java.lang.IllegalAccessException { Field idField = getIdField(modelName); Object idKey = idField.get(dataObj); Field rangeField = getRangeField(modelName); Object rangeKey = rangeField == null ? null : rangeField.get(dataObj); return makePkKey(modelName, idKey, rangeKey); } String makePkKey(String modelName, Object idKey, Object rangeKey) { String dbId = getDb(modelName).getDbTypeId(); String idStr = DataUtil.encodeValueToAttrStr(idKey, getIdField(modelName).getType()); Field rangeField = getRangeField(modelName); return rangeField == null ? idStr : idStr + "/" + DataUtil.encodeValueToAttrStr(rangeKey, rangeField.getType()); } // Registration helper methods private void validateClass(Class modelClass) { } private void validateFields(Class modelClass, Field idField, Field rangeField) { if (idField == null) throw new ValidationException("Missing the annotated @Id field in the model class."); if (Modifier.isTransient(idField.getModifiers()) || ReflectUtil.hasAnnotation(idField, Transient.class)) throw new ValidationException( "The @Key field cannot be transient or annotated with Transient in the model class."); if (rangeField != null && (Modifier.isTransient(idField.getModifiers()) || Modifier.isTransient(rangeField.getModifiers()))) throw new ValidationException("The @Key field cannot be transient in the model class."); if (ReflectUtil.hasAnnotation(idField, S3Field.class)) throw new ValidationException("The @Key field cannot be @S3Field in the model class."); if (rangeField != null && ReflectUtil.hasAnnotation(rangeField, S3Field.class)) throw new ValidationException("The @Key field cannot be @S3Field in the model class."); if (idField.getType() != String.class && idField.getType() != Integer.class && idField.getType() != int.class && idField.getType() != Long.class && idField.getType() != long.class) throw new ValidationException("The @Id field can only be String, Integer, or Long."); } private Field toKeyField(Class modelClass, List<Field> allFields, boolean returnRangeKey) { Field idField = null; Field hashKeyField = null; Field rangeKeyField = null; for (Field field : allFields) { if (!ReflectUtil.hasAnnotation(field, Key.class)) continue; if (ReflectUtil.getAnnotationValueEx(field, Key.class, "hashKey", Boolean.class, Boolean.FALSE)) { if (hashKeyField == null) { hashKeyField = field; } else { throw new IllegalArgumentException( "Only one field can be annotated as haskKey with @Key(hashKey=true)."); } } else if (ReflectUtil.getAnnotationValueEx(field, Key.class, "rangeKey", Boolean.class, Boolean.FALSE)) { if (rangeKeyField == null) { rangeKeyField = field; } else { throw new IllegalArgumentException( "Only one field can be annotated as rangeKey with @Key(rangeKey=true)."); } } else if (ReflectUtil.getAnnotationValueEx(field, Key.class, "id", Boolean.class, Boolean.FALSE)) { if (idField == null) { idField = field; } else { throw new IllegalArgumentException( "Only one field can be annotated as primary key with @Key(id=true)."); } } } if (idField != null && (hashKeyField != null || rangeKeyField != null)) throw new IllegalArgumentException( "@Key(id=true) and @Key(hashKey=ture) cannot be specified in the same class."); if (returnRangeKey) { return rangeKeyField; } if (idField != null) return idField; else return hashKeyField; } private DbService getDbService(DbType dbtype) { if (dbtype == null) throw new IllegalArgumentException("Missing 'dbtype' parameter"); if (dbtype == DbType.SimpleDB) return sdbMgr; if (dbtype == DbType.DynamoDB) return ddbMgr; throw new IllegalArgumentException(dbtype + " is not a supported dbtype"); } private DbService toDbService(Class modelClass, DbType dbtype) { if (dbtype == null) { try { dbtype = ReflectUtil.getAnnotationValue(modelClass, Model.class, "dbtype", DbType.class, null); } catch (Exception e) { throw new IllegalArgumentException( "Error in getting dbtype from the Model annotation for " + modelClass, e); } if (dbtype == null || dbtype == DbType.None) throw new IllegalArgumentException( "No valid 'dbtype' specification in the Model annotation for " + modelClass); } return getDbService(dbtype); } private String toTableName(Class modelClass) { String modelName = getModelName(modelClass); String tableName = ReflectUtil.getAnnotationValue(modelClass, Model.class, "table", modelName); // default to modelName String prefix = ReflectUtil.getAnnotationValue(modelClass, Model.class, "prefix", ""); if (!StringUtils.isEmpty(globalPrefix)) prefix = globalPrefix + prefix; return prefix + tableName; } private void toCachePolicy(String modelName, Class modelClass) throws Exception { // See if class has implemented Serializable List<Class> allInterfaces = ReflectUtil.getAllInterfaces(modelClass); boolean hasSerializable = FnUtil.fold(false, allInterfaces, new FoldFn<Boolean, Class>() { public Boolean apply(Boolean hasSerializable, Class intf) { return hasSerializable || (intf == Serializable.class); } }); // Decide CachePolicy boolean cacheable = ReflectUtil.getAnnotationValue(modelClass, CachePolicy.class, "cacheable", Boolean.class, Boolean.TRUE); // default is true if (hasSerializable) { if (cacheable) { // Serializable and Cacheable. Can cache. int expireInSeconds = ReflectUtil.getAnnotationValue(modelClass, CachePolicy.class, "expireInSeconds", Integer.class, 0); modelCachePolicy.put(modelName, new Integer(expireInSeconds)); return; } } else { if (ReflectUtil.hasAnnotation(modelClass, CachePolicy.class)) { // Not Serializable but CachePolicy specified. Specification conflict. throw new IllegalArgumentException("Model class " + modelClass.getName() + " must implement the java.io.Serializable when specifying CachePolicy for caching."); } } // Don't cache objects of the model. modelCachePolicy.put(modelName, new Integer(-1)); } private Map<String, Field> toFieldMap(List<Field> fields) { Map<String, Field> map = new ConcurrentHashMap<String, Field>(); for (Field field : fields) map.put(field.getName(), field); return map; } private Map<String, Field> toAttrFieldMap(List<Field> fields) throws Exception { Map<String, Field> map = new ConcurrentHashMap<String, Field>(); for (Field field : fields) { String attrName = ReflectUtil.getAnnotationValue(field, AttrName.class, "value", field.getName()); map.put(attrName, field); } return map; } private Map<String, String> toFieldAttrMap(List<Field> fields) throws Exception { Map<String, String> map = new ConcurrentHashMap<String, String>(); for (Field field : fields) { String attrName = ReflectUtil.getAnnotationValue(field, AttrName.class, "value", field.getName()); map.put(field.getName(), attrName); } return map; } private void toAnnotatedMethods(String modelName, Class modelClass) throws Exception { for (Method method : ReflectUtil.getAllMethods(modelClass)) { if (ReflectUtil.hasAnnotation(method, PrePersist.class)) modelPrePersistMethod.put(modelName, method); if (ReflectUtil.hasAnnotation(method, PreValidation.class)) modelPreValidationMethod.put(modelName, method); if (ReflectUtil.hasAnnotation(method, PostLoad.class)) modelPostLoadMethod.put(modelName, method); } } private Set<String> toCacheByFields(List<Field> fields) throws Exception { Set<String> set = new HashSet<String>(); for (Field field : fields) { if (ReflectUtil.hasAnnotation(field, CacheByField.class)) set.add(field.getName()); } return set; } /** * Perform the pre-store data transformation steps to call the data generators, conversion. * Note that the validations are not called. This is for initializing a data object. */ void preStoreTransformSteps(Object dataObj) throws Exception { if (dataObj == null) return; String modelName = getModelName(dataObj.getClass()); validateRegisteredModel(modelName); if (getPrePersistMethod(modelName) != null) getPrePersistMethod(modelName).invoke(dataObj); preStore1Registry.applyFieldHandlers(dataObj, modelAllFieldMap.get(modelName)); preStore2Registry.applyFieldHandlers(dataObj, modelAllFieldMap.get(modelName)); } /** * Perform the pre-store steps to call the data generators, conversion, and validation on the object. * The object is not stored yet; only its fields are transformed. * Call this directly for debugging data conversion and validation. */ public void preStoreSteps(Object dataObj) throws Exception { if (dataObj == null) return; String modelName = getModelName(dataObj.getClass()); preStoreTransformSteps(dataObj); if (getPreValidationMethod(modelName) != null) getPreValidationMethod(modelName).invoke(dataObj); validationRegistry.applyFieldHandlers(dataObj, modelAllFieldMap.get(modelName)); } /** * Perform the post-loading data transformation steps to call the data generators, conversion. * Note that the validations are not called. This is for building transient fields of a data object after loading. */ void postLoadTransformSteps(Object dataObj) throws Exception { if (dataObj == null) return; String modelName = getModelName(dataObj.getClass()); validateRegisteredModel(modelName); if (getPostLoadMethod(modelName) != null) getPostLoadMethod(modelName).invoke(dataObj); postLoadRegistry.applyFieldHandlers(dataObj, modelAllFieldMap.get(modelName)); } public void postLoadSteps(Object dataObj, boolean toCache) throws Exception { if (dataObj == null) return; String modelName = getModelName(dataObj.getClass()); postLoadTransformSteps(dataObj); if (toCache) getObjCacheMgr().cachePut(modelName, dataObj); } public void postLoadSteps(Object dataObj) throws Exception { postLoadSteps(dataObj, true); } }