/* * Copyright 2004-2010 the Seasar Foundation and the Others. * * 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.slim3.datastore; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.slim3.datastore.json.JsonArrayReader; import org.slim3.datastore.json.JsonReader; import org.slim3.datastore.json.JsonRootReader; import org.slim3.datastore.json.JsonWriter; import org.slim3.datastore.json.ModelReader; import org.slim3.datastore.json.ModelWriter; import org.slim3.util.BeanDesc; import org.slim3.util.BeanUtil; import org.slim3.util.ByteUtil; import org.slim3.util.Cipher; import org.slim3.util.CipherFactory; import com.google.appengine.api.datastore.AsyncDatastoreService; import com.google.appengine.api.datastore.Blob; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.ShortBlob; import com.google.appengine.api.datastore.Text; /** * A meta data of model. * * @author higa * @param <M> * the model type * @since 1.0.0 * */ public abstract class ModelMeta<M> { /** * The kind of entity. */ protected String kind; /** * The model class. */ protected Class<M> modelClass; /** * The list of class hierarchies. If you create polymorphic models such as A * -> B -> C, the list of A is empty, the list of B is [B], the list of c * is[B, C]. */ protected List<String> classHierarchyList; /** * The bean descriptor. */ protected BeanDesc beanDesc; /** * Constructor. * * @param kind * the kind of entity * @param modelClass * the model class * @throws NullPointerException * if the modelClass parameter is null */ public ModelMeta(String kind, Class<M> modelClass) throws NullPointerException { this(kind, modelClass, null); } /** * Constructor. * * @param kind * the kind of entity * @param modelClass * the model class * @param classHierarchyList * the list of class hierarchies * @throws NullPointerException * if the modelClass parameter is null */ @SuppressWarnings("unchecked") public ModelMeta(String kind, Class<M> modelClass, List<String> classHierarchyList) throws NullPointerException { if (kind == null) { throw new NullPointerException("The kind parameter is null."); } if (modelClass == null) { throw new NullPointerException("The modelClass parameter is null."); } this.kind = kind; this.modelClass = modelClass; if (classHierarchyList == null) { this.classHierarchyList = Collections.EMPTY_LIST; } else { this.classHierarchyList = Collections.unmodifiableList(classHierarchyList); } } /** * Constructor. */ protected ModelMeta() { } /** * Returns the kind of entity. * * @return the kind of entity */ public String getKind() { return kind; } /** * Returns the model class. * * @return the model class */ public Class<M> getModelClass() { return modelClass; } /** * Returns the list of class hierarchies. * * @return the list of class hierarchies */ public List<String> getClassHierarchyList() { return classHierarchyList; } /** * Returns the schemaVersion property name. * * @return the schemaVersion property name */ public abstract String getSchemaVersionName(); /** * Returns the classHierarchyList property name. * * @return the classHierarchyList property name */ public abstract String getClassHierarchyListName(); /** * Converts the entity to a model. * * @param entity * the entity * @return a model */ public abstract M entityToModel(Entity entity); /** * Converts the model to an entity. * * @param model * the model * @return an entity */ public abstract Entity modelToEntity(Object model); /** * Converts the model to JSON string assuming maxDepth is 0. * * @param model * the model * * @return JSON string */ public String modelToJson(Object model){ return modelToJson(model, 0); } /** * Converts the model to JSON string. * * @param model * the model * * @param maxDepth * the max depth of ModelRef expanding * * @return JSON string */ public String modelToJson(final Object model, int maxDepth){ StringBuilder b = new StringBuilder(); JsonWriter w = new JsonWriter(b, new ModelWriter() { @Override public void write(JsonWriter writer, Object model, int maxDepth, int currentDepth) { invokeModelToJson( Datastore.getModelMeta(model.getClass()), writer, model, maxDepth, currentDepth + 1); } }); modelToJson(w, model, maxDepth, 0); return b.toString(); } /** * Converts the models to JSON string. * * @param models * models * * @return JSON string */ public String modelsToJson(final Object[] models){ return modelsToJson(models, 0); } /** * Converts the models to JSON string. * * @param models * models * * @param maxDepth * the max depth of ModelRef expanding * * @return JSON string */ public String modelsToJson(final Object[] models, int maxDepth){ int n = models.length; if(n == 0) return "[]"; StringBuilder b = new StringBuilder(); JsonWriter w = new JsonWriter(b, new ModelWriter() { @Override public void write(JsonWriter writer, Object model, int maxDepth, int currentDepth) { invokeModelToJson( Datastore.getModelMeta(model.getClass()), writer, model, maxDepth, currentDepth + 1); } }); b.append("["); modelToJson(w, models[0], maxDepth, 0); for(int i = 1; i < n; i++){ b.append(","); modelToJson(w, models[i], maxDepth, 0); } b.append("]"); return b.toString(); } /** * Converts the models to JSON string. * * @param models * models * * @return JSON string */ public String modelsToJson(Iterable<?> models){ return modelsToJson(models, 0); } /** * Converts the models to JSON string. * * @param models * models * * @param maxDepth * the max depth of ModelRef expanding * * @return JSON string */ public String modelsToJson(final Iterable<?> models, int maxDepth){ StringBuilder b = new StringBuilder(); JsonWriter w = new JsonWriter(b, new ModelWriter() { @Override public void write(JsonWriter writer, Object model, int maxDepth, int currentDepth) { invokeModelToJson( Datastore.getModelMeta(model.getClass()), writer, model, maxDepth, currentDepth + 1); } }); b.append("["); boolean first = true; for(Object o : models){ if(first){ first = false; } else{ b.append(","); } modelToJson(w, o, maxDepth, 0); } b.append("]"); return b.toString(); } /** * Converts the model to JSON string. * * @param writer * the writer * * @param model * the model * * @param maxDepth * the max depth * * @param currentDepth * the current depth */ protected abstract void modelToJson(JsonWriter writer, Object model, int maxDepth, int currentDepth); /** * Invoke the modelToJson method. * * @param meta * the meta * * @param writer * the writer * * @param model * the model * * @param maxDepth * the max depth * * @param currentDepth * the current depth */ protected void invokeModelToJson(ModelMeta<?> meta, JsonWriter writer , Object model, int maxDepth, int currentDepth){ meta.modelToJson(writer, model, maxDepth, currentDepth); } /** * Converts the JSON string to model. * * @param json * the JSON string * * @return model */ public M jsonToModel(String json){ return jsonToModel(json, 0); } /** * Converts the JSON string to model. * * @param json * the JSON string * * @param maxDepth * the max depth * * @return model */ public M jsonToModel(String json, int maxDepth){ return jsonToModel(json, maxDepth, 0); } /** * Converts the JSON string to model array. * * @param json * the JSON string * * @return model array */ public M[] jsonToModels(String json){ return jsonToModels(json, 0); } /** * Converts the JSON string to model array. * * @param json * the JSON string * * @param maxDepth * the max depth * * @return model array */ @SuppressWarnings("unchecked") public M[] jsonToModels(String json, int maxDepth){ JsonArrayReader ar = new JsonArrayReader(json, new ModelReader() { @Override public <T> T read(JsonReader reader, Class<T> modelClass, int maxDepth, int currentDepth) { return invokeJsonToModel( Datastore.getModelMeta(modelClass), reader, maxDepth, currentDepth + 1); } }); M[] ret = (M[])Array.newInstance(this.getModelClass(), ar.length()); for(int i = 0; i < ar.length(); i++){ ar.setIndex(i); ret[i] = jsonToModel(ar.newRootReader(), maxDepth, 0); } return ret; } /** * Converts the JSON string to model. * * @param json * the JSON string * * @param maxDepth * the max depth * * @param currentDepth * the current depth * * @return model */ protected M jsonToModel(String json, int maxDepth, int currentDepth){ return jsonToModel(new JsonRootReader(json, new ModelReader() { @Override public <T> T read(JsonReader reader, Class<T> modelClass, int maxDepth, int currentDepth) { return invokeJsonToModel( Datastore.getModelMeta(modelClass), reader, maxDepth, currentDepth + 1); } }) , maxDepth, currentDepth); } /** * Converts the JSON string to model. * * @param reader * the JSON reader * * @param maxDepth * the max depth * * @param currentDepth * the current depth * * @return model */ protected abstract M jsonToModel(JsonRootReader reader, int maxDepth, int currentDepth); /** * Converts the JSON string to model. * * @param <T> * the type of model * * @param meta * the model meta * * @param reader * the JSON reader * * @param maxDepth * the max depth * * @param currentDepth * the current depth * * @return model */ protected <T> T invokeJsonToModel(ModelMeta<T> meta, JsonReader reader , int maxDepth, int currentDepth){ return meta.jsonToModel(reader.read(), maxDepth, currentDepth); } /** * Returns version property value of the model. * * @param model * the model * @return a version property value of the model */ protected abstract long getVersion(Object model); /** * Increments the version property value. * * @param model * the model */ protected abstract void incrementVersion(Object model); /** * This method is called before a model is put to datastore. * * @param model * the model */ protected abstract void prePut(Object model); /** * This method is called after a model is get from datastore. * * @param model * the model */ protected abstract void postGet(Object model); /** * Returns a key of the model. * * @param model * the model * @return a key of the model */ protected abstract Key getKey(Object model); /** * Sets the key to the model. * * @param model * the model * @param key * the key */ protected abstract void setKey(Object model, Key key); /** * Assigns a key to {@link ModelRef} if necessary. * * @param ds * the asynchronous datastore service * @param model * the model * @throws NullPointerException * if the ds parameter is null or if the model parameter is null */ protected abstract void assignKeyToModelRefIfNecessary( AsyncDatastoreService ds, Object model) throws NullPointerException; /** * Validates the kind of the key. * * @param key * the key * @throws IllegalArgumentException * if the kind of the key is different from the kind of this * model */ protected void validateKey(Key key) throws IllegalArgumentException { if (key != null && !key.getKind().equals(kind)) { throw new IllegalArgumentException("The kind(" + key.getKind() + ") of the key(" + key + ") must be " + kind + "."); } } /** * Converts the long to a primitive short. * * @param value * the long * @return a primitive short */ protected short longToPrimitiveShort(Long value) { return value != null ? value.shortValue() : 0; } /** * Converts the long to a short. * * @param value * the long * @return a short */ protected Short longToShort(Long value) { return value != null ? value.shortValue() : null; } /** * Converts the long to a primitive int. * * @param value * the long * @return a primitive int */ protected int longToPrimitiveInt(Long value) { return value != null ? value.intValue() : 0; } /** * Converts the long to an integer. * * @param value * the long * @return an integer */ protected Integer longToInteger(Long value) { return value != null ? value.intValue() : null; } /** * Converts the long to a primitive long. * * @param value * the long * @return a primitive long */ protected long longToPrimitiveLong(Long value) { return value != null ? value : 0; } /** * Converts the double to a primitive float. * * @param value * the double * @return a primitive float */ protected float doubleToPrimitiveFloat(Double value) { return value != null ? value.floatValue() : 0; } /** * Converts the double to a float. * * @param value * the double * @return a float */ protected Float doubleToFloat(Double value) { return value != null ? value.floatValue() : null; } /** * Converts the double to a primitive double. * * @param value * the double * @return a primitive double */ protected double doubleToPrimitiveDouble(Double value) { return value != null ? value : 0; } /** * Converts the boolean to a primitive boolean. * * @param value * the boolean * @return a primitive boolean */ protected boolean booleanToPrimitiveBoolean(Boolean value) { return value != null ? value : false; } /** * Converts the {@link Enum} to a string representation. * * @param value * the {@link Enum} * @return a string representation */ protected String enumToString(Enum<?> value) { return value != null ? value.name() : null; } /** * Converts the string to an {@link Enum}. * * @param <T> * the enum type * @param clazz * the enum class * @param value * the String * @return an {@link Enum} */ protected <T extends Enum<T>> T stringToEnum(Class<T> clazz, String value) { return value != null ? Enum.valueOf(clazz, value) : null; } /** * Converts the text to a string * * @param value * the text * @return a string */ protected String textToString(Text value) { return value != null ? value.getValue() : null; } /** * Converts the string to a text * * @param value * the string * @return a text */ protected Text stringToText(String value) { return value != null ? new Text(value) : null; } /** * Converts the short blob to an array of bytes. * * @param value * the short blob * @return an array of bytes */ protected byte[] shortBlobToBytes(ShortBlob value) { return value != null ? value.getBytes() : null; } /** * Converts the array of bytes to a short blob. * * @param value * the array of bytes * @return a short blob */ protected ShortBlob bytesToShortBlob(byte[] value) { return value != null ? new ShortBlob(value) : null; } /** * Converts the blob to an array of bytes. * * @param value * the blob * @return an array of bytes */ protected byte[] blobToBytes(Blob value) { return value != null ? value.getBytes() : null; } /** * Converts the array of bytes to a blob. * * @param value * the array of bytes * @return a blob */ protected Blob bytesToBlob(byte[] value) { return value != null ? new Blob(value) : null; } /** * Converts the short blob to a serializable object. * * @param <T> * the type * @param value * the short blob * @return a serializable object */ @SuppressWarnings("unchecked") protected <T> T shortBlobToSerializable(ShortBlob value) { return value != null ? (T) ByteUtil.toObject(value.getBytes()) : null; } /** * Converts the serializable object to a short blob. * * @param value * the serializable object * @return a short blob */ protected ShortBlob serializableToShortBlob(Object value) { return value != null ? new ShortBlob(ByteUtil.toByteArray(value)) : null; } /** * Converts the blob to a serializable object. * * @param <T> * the type * @param value * the blob * @return a serializable object */ @SuppressWarnings("unchecked") protected <T> T blobToSerializable(Blob value) { return value != null ? (T) ByteUtil.toObject(value.getBytes()) : null; } /** * Converts the serializable object to a blob. * * @param value * the serializable object * @return a blob */ protected Blob serializableToBlob(Object value) { return value != null ? new Blob(ByteUtil.toByteArray(value)) : null; } /** * Converts the list to an array list. * * @param <T> * the type * @param clazz * the class * @param value * the list * @return an array list */ @SuppressWarnings("unchecked") protected <T> ArrayList<T> toList(Class<T> clazz, Object value) { if (value == null) { return new ArrayList<T>(); } return (ArrayList<T>) value; } /** * Converts the list of long to a list of short. * * @param value * the list of long * @return a list of short */ @SuppressWarnings("unchecked") protected ArrayList<Short> longListToShortList(Object value) { List<Long> v = (List<Long>) value; if (v == null) { return new ArrayList<Short>(); } ArrayList<Short> collection = new ArrayList<Short>(v.size()); int size = v.size(); for (int i = 0; i < size; i++) { Long l = v.get(i); collection.add(l != null ? l.shortValue() : null); } return collection; } /** * Converts the list of long to a list of integer. * * @param value * the list of long * @return a list of integer */ @SuppressWarnings("unchecked") protected ArrayList<Integer> longListToIntegerList(Object value) { List<Long> v = (List<Long>) value; if (v == null) { return new ArrayList<Integer>(); } ArrayList<Integer> collection = new ArrayList<Integer>(v.size()); int size = v.size(); for (int i = 0; i < size; i++) { Long l = v.get(i); collection.add(l != null ? l.intValue() : null); } return collection; } /** * Converts the list of double to a list of float. * * @param value * the list of double * @return a list of float */ @SuppressWarnings("unchecked") protected ArrayList<Float> doubleListToFloatList(Object value) { List<Double> v = (List<Double>) value; if (v == null) { return new ArrayList<Float>(); } ArrayList<Float> collection = new ArrayList<Float>(v.size()); int size = v.size(); for (int i = 0; i < size; i++) { Double d = v.get(i); collection.add(d != null ? d.floatValue() : null); } return collection; } /** * Converts the list of {@link Enum}s to a list of strings. * * @param value * the list of {@link Enum} * @return a list of strings */ @SuppressWarnings("unchecked") protected List<String> enumListToStringList(Object value) { List<Enum<?>> v = (List<Enum<?>>) value; if (v == null) { return new ArrayList<String>(); } List<String> list = new ArrayList<String>(v.size()); for (Enum<?> e : v) { list.add(e.name()); } return list; } /** * Converts the list of strings to a list of {@link Enum}s. * * @param <T> * the enum type * @param clazz * the enum class * @param value * the list of strings * @return a list of {@link Enum}s */ @SuppressWarnings("unchecked") protected <T extends Enum<T>> List<T> stringListToEnumList(Class<T> clazz, Object value) { List<String> v = (List<String>) value; if (v == null) { return new ArrayList<T>(); } List<T> list = new ArrayList<T>(v.size()); for (String s : v) { list.add(s != null ? Enum.valueOf(clazz, s) : null); } return list; } /** * Returns the bean descriptor. * * @return the bean descriptor */ protected BeanDesc getBeanDesc() { if (beanDesc != null) { return beanDesc; } beanDesc = BeanUtil.getBeanDesc(modelClass); return beanDesc; } /** * Determines if the property is cipher. * * @param propertyName * the property name * @return whether property is cipher * @since 1.0.6 */ protected boolean isCipherProperty(String propertyName) { return false; } /** * Encrypt the text. * * @param text * the text * @return the encrypted text * @since 1.0.6 */ protected String encrypt(String text) { Cipher c = CipherFactory.getFactory().createCipher(); return c.encrypt(text); } /** * Encrypt the text. * * @param text * the text * @return the encrypted text * @since 1.0.6 */ protected Text encrypt(Text text) { if (text == null) return null; return new Text(encrypt(text.getValue())); } /** * Decrypt the encrypted text. * * @param encryptedText * the encrypted text * @return the decrypted text * @since 1.0.6 */ protected String decrypt(String encryptedText) { Cipher c = CipherFactory.getFactory().createCipher(); return c.decrypt(encryptedText); } /** * Decrypt the encrypted text. * * @param encryptedText * the encrypted text * @return the decrypted text * @since 1.0.6 */ protected Text decrypt(Text encryptedText) { if (encryptedText == null) return null; return new Text(decrypt(encryptedText.getValue())); } }