
com.orientechnologies.orient.object.enhancement.OObjectEntitySerializer Maven / Gradle / Ivy
The newest version!
/*
*
* Copyright 2012 Luca Molino (molino.luca--AT--gmail.com)
*
* 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 com.orientechnologies.orient.object.enhancement;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javassist.util.proxy.Proxy;
import javassist.util.proxy.ProxyObject;
import javax.persistence.CascadeType;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.profiler.OProfiler;
import com.orientechnologies.common.reflection.OReflectionHelper;
import com.orientechnologies.orient.core.annotation.OAccess;
import com.orientechnologies.orient.core.annotation.OAfterDeserialization;
import com.orientechnologies.orient.core.annotation.OAfterSerialization;
import com.orientechnologies.orient.core.annotation.OBeforeDeserialization;
import com.orientechnologies.orient.core.annotation.OBeforeSerialization;
import com.orientechnologies.orient.core.annotation.ODocumentInstance;
import com.orientechnologies.orient.core.annotation.OId;
import com.orientechnologies.orient.core.annotation.OVersion;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.object.ODatabaseObject;
import com.orientechnologies.orient.core.db.record.ORecordLazyList;
import com.orientechnologies.orient.core.db.record.ORecordLazyMap;
import com.orientechnologies.orient.core.db.record.ORecordLazySet;
import com.orientechnologies.orient.core.db.record.OTrackedList;
import com.orientechnologies.orient.core.db.record.OTrackedMap;
import com.orientechnologies.orient.core.db.record.OTrackedSet;
import com.orientechnologies.orient.core.exception.OConfigurationException;
import com.orientechnologies.orient.core.exception.OSerializationException;
import com.orientechnologies.orient.core.exception.OTransactionException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OProperty;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.tx.OTransactionOptimistic;
import com.orientechnologies.orient.object.db.OObjectLazyMap;
import com.orientechnologies.orient.object.serialization.OObjectSerializationThreadLocal;
import com.orientechnologies.orient.object.serialization.OObjectSerializerHelper;
/**
* @author luca.molino
*
*/
public class OObjectEntitySerializer {
private static final Set> classes = new HashSet>();
private static final HashMap, List> embeddedFields = new HashMap, List>();
private static final HashMap, List> directAccessFields = new HashMap, List>();
private static final HashMap, Field> boundDocumentFields = new HashMap, Field>();
private static final HashMap, List> transientFields = new HashMap, List>();
private static final HashMap, List> cascadeDeleteFields = new HashMap, List>();
private static final HashMap, Map>> serializedFields = new HashMap, Map>>();
private static final HashMap, Field> fieldIds = new HashMap, Field>();
private static final HashMap, Field> fieldVersions = new HashMap, Field>();
private static final HashMap> callbacks = new HashMap>();
/**
* Method that given an object serialize it an creates a proxy entity, in case the object isn't generated using the
* ODatabaseObject.newInstance()
*
* @param o
* - the object to serialize
* @return the proxied object
*/
public static T serializeObject(T o, ODatabaseObject db) {
if (o instanceof Proxy) {
final ODocument iRecord = getDocument((Proxy) o);
Class> pojoClass = o.getClass().getSuperclass();
invokeCallback(pojoClass, o, iRecord, OBeforeSerialization.class);
invokeCallback(pojoClass, o, iRecord, OAfterSerialization.class);
return o;
}
Proxy proxiedObject = (Proxy) db.newInstance(o.getClass());
try {
return toStream(o, proxiedObject, db);
} catch (IllegalArgumentException e) {
throw new OSerializationException("Error serializing object of class " + o.getClass(), e);
} catch (IllegalAccessException e) {
throw new OSerializationException("Error serializing object of class " + o.getClass(), e);
}
}
/**
* Method that attaches all data contained in the object to the associated document
*
* @param
* @param o
* :- the object to attach
* @param db
* :- the database instance
* @return the object serialized or with attached data
*/
public static T attach(T o, ODatabaseObject db) {
if (o instanceof Proxy) {
OObjectProxyMethodHandler handler = (OObjectProxyMethodHandler) ((ProxyObject) o).getHandler();
try {
handler.attach(o);
} catch (IllegalArgumentException e) {
throw new OSerializationException("Error detaching object of class " + o.getClass(), e);
} catch (IllegalAccessException e) {
throw new OSerializationException("Error detaching object of class " + o.getClass(), e);
} catch (NoSuchMethodException e) {
throw new OSerializationException("Error detaching object of class " + o.getClass(), e);
} catch (InvocationTargetException e) {
throw new OSerializationException("Error detaching object of class " + o.getClass(), e);
}
return o;
} else
return serializeObject(o, db);
}
/**
* Method that detaches all fields contained in the document to the given object
*
* @param
* @param o
* :- the object to detach
* @param db
* :- the database instance
* @return the object serialized or with detached data
*/
public static T detach(T o, ODatabaseObject db) {
if (o instanceof Proxy) {
OObjectProxyMethodHandler handler = (OObjectProxyMethodHandler) ((ProxyObject) o).getHandler();
try {
handler.detach(o);
} catch (IllegalArgumentException e) {
throw new OSerializationException("Error detaching object of class " + o.getClass(), e);
} catch (IllegalAccessException e) {
throw new OSerializationException("Error detaching object of class " + o.getClass(), e);
} catch (NoSuchMethodException e) {
throw new OSerializationException("Error detaching object of class " + o.getClass(), e);
} catch (InvocationTargetException e) {
throw new OSerializationException("Error detaching object of class " + o.getClass(), e);
}
return o;
} else
return serializeObject(o, db);
}
/**
* Method that given a proxied entity returns the associated ODocument
*
* @param proxiedObject
* - the proxied entity object
* @return The ODocument associated with the object
*/
public static ODocument getDocument(Proxy proxiedObject) {
return ((OObjectProxyMethodHandler) ((ProxyObject) proxiedObject).getHandler()).getDoc();
}
/**
* Method that given a proxied entity returns the associated ODocument RID
*
* @param proxiedObject
* - the proxied entity object
* @return The ORID of associated ODocument
*/
public static ORID getRid(Proxy proxiedObject) {
return getDocument(proxiedObject).getIdentity();
}
/**
* Method that given a proxied entity returns the associated ODocument version
*
* @param proxiedObject
* - the proxied entity object
* @return The version of associated ODocument
*/
public static int getVersion(Proxy proxiedObject) {
return getDocument(proxiedObject).getVersion();
}
public static boolean isTransientField(Class> iClass, String iField) {
checkClassRegistration(iClass);
boolean isTransientField = false;
for (Class> currentClass = iClass; currentClass != null && currentClass != Object.class
&& !currentClass.equals(ODocument.class) && !isTransientField;) {
List classCascadeDeleteFields = transientFields.get(currentClass);
isTransientField = classCascadeDeleteFields != null && classCascadeDeleteFields.contains(iField);
currentClass = currentClass.getSuperclass();
}
return isTransientField;
}
public static List getCascadeDeleteFields(Class> iClass) {
checkClassRegistration(iClass);
List classCascadeDeleteFields = new ArrayList();
for (Class> currentClass = iClass; currentClass != null && currentClass != Object.class
&& !currentClass.equals(ODocument.class);) {
List classDeleteFields = cascadeDeleteFields.get(currentClass);
if (classDeleteFields != null)
classCascadeDeleteFields.addAll(classDeleteFields);
currentClass = currentClass.getSuperclass();
}
return classCascadeDeleteFields;
}
public static List getCascadeDeleteFields(String iClassName) {
if (iClassName == null || iClassName.isEmpty())
return null;
for (Class> iClass : cascadeDeleteFields.keySet()) {
if (iClass.getSimpleName().equals(iClassName))
return getCascadeDeleteFields(iClass);
}
return null;
}
public static boolean isCascadeDeleteField(Class> iClass, String iField) {
checkClassRegistration(iClass);
boolean isTransientField = false;
for (Class> currentClass = iClass; currentClass != null && currentClass != Object.class
&& !currentClass.equals(ODocument.class) && !isTransientField;) {
List classEmbeddedFields = cascadeDeleteFields.get(currentClass);
isTransientField = classEmbeddedFields != null && classEmbeddedFields.contains(iField);
currentClass = currentClass.getSuperclass();
}
return isTransientField;
}
public static boolean isEmbeddedField(Class> iClass, String iField) {
checkClassRegistration(iClass);
boolean isEmbeddedField = false;
for (Class> currentClass = iClass; currentClass != null && currentClass != Object.class
&& !currentClass.equals(ODocument.class) && !isEmbeddedField;) {
List classEmbeddedFields = embeddedFields.get(currentClass);
isEmbeddedField = classEmbeddedFields != null && classEmbeddedFields.contains(iField);
currentClass = currentClass.getSuperclass();
}
return isEmbeddedField;
}
protected static void checkClassRegistration(Class> iClass) {
if (!classes.contains(iClass) && !(Proxy.class.isAssignableFrom(iClass)))
registerClass(iClass);
}
/**
* Registers the class informations that will be used in serialization, deserialization and lazy loading of it. If already
* registered does nothing.
*
* @param iClass
* :- the Class> to register
*/
@SuppressWarnings("unchecked")
public static synchronized void registerClass(Class> iClass) {
if (classes.contains(iClass))
return;
if (ODatabaseRecordThreadLocal.INSTANCE.isDefined() && !ODatabaseRecordThreadLocal.INSTANCE.get().isClosed()
&& !ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getSchema().existsClass(iClass.getSimpleName()))
ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getSchema().createClass(iClass.getSimpleName());
for (Class> currentClass = iClass; currentClass != Object.class;) {
if (!classes.contains(currentClass)) {
classes.add(currentClass);
Class> fieldType;
for (Field f : currentClass.getDeclaredFields()) {
final String fieldName = f.getName();
final int fieldModifier = f.getModifiers();
if (Modifier.isStatic(fieldModifier) || Modifier.isNative(fieldModifier) || Modifier.isTransient(fieldModifier))
continue;
if (fieldName.equals("this$0")) {
List classTransientFields = transientFields.get(currentClass);
if (classTransientFields == null)
classTransientFields = new ArrayList();
classTransientFields.add(fieldName);
transientFields.put(currentClass, classTransientFields);
}
if (OObjectSerializerHelper.jpaTransientClass != null) {
Annotation ann = f.getAnnotation(OObjectSerializerHelper.jpaTransientClass);
if (ann != null) {
// @Transient DEFINED
List classTransientFields = transientFields.get(currentClass);
if (classTransientFields == null)
classTransientFields = new ArrayList();
classTransientFields.add(fieldName);
transientFields.put(currentClass, classTransientFields);
}
}
if (OObjectSerializerHelper.jpaOneToOneClass != null) {
Annotation ann = f.getAnnotation(OObjectSerializerHelper.jpaOneToOneClass);
if (ann != null) {
// @OneToOne DEFINED
OneToOne oneToOne = ((OneToOne) ann);
if (checkCascadeDelete(oneToOne)) {
addCascadeDeleteField(currentClass, fieldName);
}
}
}
if (OObjectSerializerHelper.jpaOneToManyClass != null) {
Annotation ann = f.getAnnotation(OObjectSerializerHelper.jpaOneToManyClass);
if (ann != null) {
// @OneToMany DEFINED
OneToMany oneToMany = ((OneToMany) ann);
if (checkCascadeDelete(oneToMany)) {
addCascadeDeleteField(currentClass, fieldName);
}
}
}
if (OObjectSerializerHelper.jpaManyToManyClass != null) {
Annotation ann = f.getAnnotation(OObjectSerializerHelper.jpaManyToManyClass);
if (ann != null) {
// @OneToMany DEFINED
ManyToMany manyToMany = ((ManyToMany) ann);
if (checkCascadeDelete(manyToMany)) {
addCascadeDeleteField(currentClass, fieldName);
}
}
}
fieldType = f.getType();
if (Collection.class.isAssignableFrom(fieldType) || fieldType.isArray() || Map.class.isAssignableFrom(fieldType)) {
fieldType = OReflectionHelper.getGenericMultivalueType(f);
}
if (isToSerialize(fieldType)) {
Map> serializeClass = serializedFields.get(currentClass);
if (serializeClass == null)
serializeClass = new HashMap>();
serializeClass.put(f, fieldType);
serializedFields.put(currentClass, serializeClass);
}
// CHECK FOR DIRECT-BINDING
boolean directBinding = true;
if (f.getAnnotation(OAccess.class) == null || f.getAnnotation(OAccess.class).value() == OAccess.OAccessType.PROPERTY)
directBinding = true;
// JPA 2+ AVAILABLE?
else if (OObjectSerializerHelper.jpaAccessClass != null) {
Annotation ann = f.getAnnotation(OObjectSerializerHelper.jpaAccessClass);
if (ann != null) {
directBinding = true;
}
}
if (directBinding) {
List classDirectAccessFields = directAccessFields.get(currentClass);
if (classDirectAccessFields == null)
classDirectAccessFields = new ArrayList();
classDirectAccessFields.add(fieldName);
directAccessFields.put(currentClass, classDirectAccessFields);
}
if (f.getAnnotation(ODocumentInstance.class) != null)
// BOUND DOCUMENT ON IT
boundDocumentFields.put(currentClass, f);
boolean idFound = false;
if (f.getAnnotation(OId.class) != null) {
// RECORD ID
fieldIds.put(currentClass, f);
idFound = true;
}
// JPA 1+ AVAILABLE?
else if (OObjectSerializerHelper.jpaIdClass != null && f.getAnnotation(OObjectSerializerHelper.jpaIdClass) != null) {
// RECORD ID
fieldIds.put(currentClass, f);
idFound = true;
}
if (idFound) {
// CHECK FOR TYPE
if (fieldType.isPrimitive())
OLogManager.instance().warn(OObjectSerializerHelper.class, "Field '%s' cannot be a literal to manage the Record Id",
f.toString());
else if (!ORID.class.isAssignableFrom(fieldType) && fieldType != String.class && fieldType != Object.class
&& !Number.class.isAssignableFrom(fieldType))
OLogManager.instance().warn(OObjectSerializerHelper.class, "Field '%s' cannot be managed as type: %s", f.toString(),
fieldType);
}
boolean vFound = false;
if (f.getAnnotation(OVersion.class) != null) {
// RECORD ID
fieldVersions.put(currentClass, f);
vFound = true;
}
// JPA 1+ AVAILABLE?
else if (OObjectSerializerHelper.jpaVersionClass != null
&& f.getAnnotation(OObjectSerializerHelper.jpaVersionClass) != null) {
// RECORD ID
fieldVersions.put(currentClass, f);
vFound = true;
}
if (vFound) {
// CHECK FOR TYPE
if (fieldType.isPrimitive())
OLogManager.instance().warn(OObjectSerializerHelper.class, "Field '%s' cannot be a literal to manage the Version",
f.toString());
else if (fieldType != String.class && fieldType != Object.class && !Number.class.isAssignableFrom(fieldType))
OLogManager.instance().warn(OObjectSerializerHelper.class, "Field '%s' cannot be managed as type: %s", f.toString(),
fieldType);
}
// JPA 1+ AVAILABLE?
if (OObjectSerializerHelper.jpaEmbeddedClass != null && f.getAnnotation(OObjectSerializerHelper.jpaEmbeddedClass) != null) {
List classEmbeddedFields = embeddedFields.get(currentClass);
if (classEmbeddedFields == null)
classEmbeddedFields = new ArrayList();
classEmbeddedFields.add(fieldName);
embeddedFields.put(currentClass, classEmbeddedFields);
}
}
registerCallbacks(currentClass);
}
String iClassName = iClass.getSimpleName();
currentClass = currentClass.getSuperclass();
if (currentClass == null || currentClass.equals(ODocument.class))
// POJO EXTENDS ODOCUMENT: SPECIAL CASE: AVOID TO CONSIDER
// ODOCUMENT FIELDS
currentClass = Object.class;
if (ODatabaseRecordThreadLocal.INSTANCE.get() != null && !ODatabaseRecordThreadLocal.INSTANCE.get().isClosed()
&& !currentClass.equals(Object.class)) {
OClass oSuperClass;
OClass currentOClass = ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getSchema().getClass(iClassName);
if (!ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getSchema().existsClass(currentClass.getSimpleName()))
oSuperClass = ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getSchema()
.createClass(currentClass.getSimpleName());
else
oSuperClass = ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getSchema().getClass(currentClass.getSimpleName());
if (currentOClass.getSuperClass() == null || !currentOClass.getSuperClass().equals(oSuperClass))
currentOClass.setSuperClass(oSuperClass);
}
}
if (ODatabaseRecordThreadLocal.INSTANCE.get() != null && !ODatabaseRecordThreadLocal.INSTANCE.get().isClosed())
ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getSchema().save();
}
protected static boolean checkCascadeDelete(OneToOne oneToOne) {
return oneToOne.orphanRemoval() || checkCascadeAnnotationAttribute(oneToOne.cascade());
}
protected static boolean checkCascadeDelete(OneToMany oneToMany) {
return oneToMany.orphanRemoval() || checkCascadeAnnotationAttribute(oneToMany.cascade());
}
protected static boolean checkCascadeDelete(ManyToMany manyToMany) {
return checkCascadeAnnotationAttribute(manyToMany.cascade());
}
protected static boolean checkCascadeAnnotationAttribute(CascadeType[] cascadeList) {
if (cascadeList == null || cascadeList.length <= 0)
return false;
for (CascadeType type : cascadeList) {
if (type.equals(CascadeType.ALL) || type.equals(CascadeType.REMOVE))
return true;
}
return false;
}
protected static void addCascadeDeleteField(Class> currentClass, final String fieldName) {
List classCascadeDeleteFields = cascadeDeleteFields.get(currentClass);
if (classCascadeDeleteFields == null)
classCascadeDeleteFields = new ArrayList();
classCascadeDeleteFields.add(fieldName);
cascadeDeleteFields.put(currentClass, classCascadeDeleteFields);
}
public static boolean isSerializedType(final Field iField) {
if (!classes.contains(iField.getDeclaringClass()))
registerCallbacks(iField.getDeclaringClass());
Map> serializerFields = serializedFields.get(iField.getDeclaringClass());
return serializerFields != null && serializerFields.get(iField) != null;
}
public static Class> getSerializedType(final Field iField) {
if (!classes.contains(iField.getDeclaringClass()))
registerCallbacks(iField.getDeclaringClass());
return serializedFields.get(iField.getDeclaringClass()) != null ? serializedFields.get(iField.getDeclaringClass()).get(iField)
: null;
}
public static boolean isToSerialize(final Class> type) {
for (Class> classContext : OObjectSerializerHelper.serializerContexts.keySet()) {
if (classContext != null && classContext.isAssignableFrom(type)) {
return true;
}
}
return OObjectSerializerHelper.serializerContexts.get(null) != null
&& OObjectSerializerHelper.serializerContexts.get(null).isClassBinded(type);
}
public static Object serializeFieldValue(final Class> type, final Object iFieldValue) {
for (Class> classContext : OObjectSerializerHelper.serializerContexts.keySet()) {
if (classContext != null && classContext.isAssignableFrom(type)) {
return OObjectSerializerHelper.serializerContexts.get(classContext).serializeFieldValue(type, iFieldValue);
}
}
if (OObjectSerializerHelper.serializerContexts.get(null) != null)
return OObjectSerializerHelper.serializerContexts.get(null).serializeFieldValue(type, iFieldValue);
return iFieldValue;
}
public static Object deserializeFieldValue(final Class> type, final Object iFieldValue) {
for (Class> classContext : OObjectSerializerHelper.serializerContexts.keySet()) {
if (classContext != null && classContext.isAssignableFrom(type)) {
return OObjectSerializerHelper.serializerContexts.get(classContext).unserializeFieldValue(type, iFieldValue);
}
}
if (OObjectSerializerHelper.serializerContexts.get(null) != null)
return OObjectSerializerHelper.serializerContexts.get(null).unserializeFieldValue(type, iFieldValue);
return iFieldValue;
}
public static Object typeToStream(Object iFieldValue, OType iType, final ODatabaseObject db, final ODocument iRecord) {
if (iFieldValue == null)
return null;
if (iFieldValue instanceof Proxy)
return getDocument((Proxy) iFieldValue);
if (!OType.isSimpleType(iFieldValue) || iFieldValue.getClass().isArray()) {
Class> fieldClass = iFieldValue.getClass();
if (fieldClass.isArray()) {
// ARRAY
final int arrayLength = Array.getLength(iFieldValue);
final List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy