All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.orientechnologies.orient.object.db.ODatabasePojoAbstract Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2010-2012 Luca Garulli (l.garulli--at--orientechnologies.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.db;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
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 com.orientechnologies.orient.core.command.OCommandRequest;
import com.orientechnologies.orient.core.db.ODatabaseComplex;
import com.orientechnologies.orient.core.db.ODatabaseSchemaAware;
import com.orientechnologies.orient.core.db.ODatabaseWrapperAbstract;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import com.orientechnologies.orient.core.db.object.OLazyObjectMultivalueElement;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordElement;
import com.orientechnologies.orient.core.exception.OConfigurationException;
import com.orientechnologies.orient.core.hook.ORecordHook;
import com.orientechnologies.orient.core.hook.ORecordHook.TYPE;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.metadata.OMetadata;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.metadata.security.OUser;
import com.orientechnologies.orient.core.query.OQuery;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.ORecordInternal;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.tx.OTransaction;
import com.orientechnologies.orient.core.tx.OTransaction.TXTYPE;
import com.orientechnologies.orient.object.enhancement.OObjectProxyMethodHandler;
import com.orientechnologies.orient.object.serialization.OObjectSerializerHelper;

@SuppressWarnings("unchecked")
public abstract class ODatabasePojoAbstract extends ODatabaseWrapperAbstract implements
		ODatabaseSchemaAware {
	protected IdentityHashMap	objects2Records	= new IdentityHashMap();
	protected IdentityHashMap				records2Objects	= new IdentityHashMap();
	protected HashMap						rid2Records			= new HashMap();
	protected boolean															retainObjects		= true;

	public ODatabasePojoAbstract(final ODatabaseDocumentTx iDatabase) {
		super(iDatabase);
		iDatabase.setDatabaseOwner(this);
	}

	public abstract ODocument pojo2Stream(final T iPojo, final ODocument record);

	public abstract Object stream2pojo(final ODocument record, final Object iPojo, final String iFetchPlan);

	@Override
	public void close() {
		objects2Records.clear();
		records2Objects.clear();
		rid2Records.clear();
		super.close();
	}

	public OTransaction getTransaction() {
		return underlying.getTransaction();
	}

	public ODatabaseComplex begin() {
		return (ODatabaseComplex) underlying.begin();
	}

	public ODatabaseComplex begin(final TXTYPE iType) {
		return (ODatabaseComplex) underlying.begin(iType);
	}

	public ODatabaseComplex begin(final OTransaction iTx) {
		return (ODatabaseComplex) underlying.begin(iTx);
	}

	public ODatabaseComplex commit() {
		clearNewEntriesFromCache();

		underlying.commit();

		return this;
	}

	public ODatabaseComplex rollback() {
		clearNewEntriesFromCache();

		underlying.rollback();

		final Set rids = new HashSet(rid2Records.keySet());

		ORecord record;
		Object object;
		for (ORID rid : rids) {
			if (rid.isTemporary()) {
				record = rid2Records.remove(rid);
				if (record != null) {
					object = records2Objects.remove(record);
					if (object != null) {
						objects2Records.remove(object);
					}
				}
			}
		}

		return this;
	}

	/**
	 * Sets as dirty a POJO. This is useful when you change the object and need to tell to the engine to treat as dirty.
	 * 
	 * @param iPojo
	 *          User object
	 */
	public void setDirty(final Object iPojo) {
		if (iPojo == null)
			return;

		final ODocument record = getRecordByUserObject(iPojo, false);
		if (record == null)
			throw new OObjectNotManagedException("The object " + iPojo + " is not managed by current database");

		record.setDirty();
	}

	/**
	 * Sets as not dirty a POJO. This is useful when you change some other object and need to tell to the engine to treat this one as
	 * not dirty.
	 * 
	 * @param iPojo
	 *          User object
	 */
	public void unsetDirty(final Object iPojo) {
		if (iPojo == null)
			return;

		final ODocument record = getRecordByUserObject(iPojo, false);
		if (record == null)
			return;

		record.unsetDirty();
	}

	public void setInternal(final ATTRIBUTES attribute, final Object iValue) {
		underlying.setInternal(attribute, iValue);
	}

	/**
	 * Returns the version number of the object. Version starts from 0 assigned on creation.
	 * 
	 * @param iPojo
	 *          User object
	 */
	public int getVersion(final Object iPojo) {
		final ODocument record = getRecordByUserObject(iPojo, false);

		if (record == null)
			throw new OObjectNotManagedException("The object " + iPojo + " is not managed by current database");

		return record.getVersion();
	}

	/**
	 * Returns the object unique identity.
	 * 
	 * @param iPojo
	 *          User object
	 */
	public ORID getIdentity(final Object iPojo) {
		final ODocument record = getRecordByUserObject(iPojo, false);
		if (record == null)
			throw new OObjectNotManagedException("The object " + iPojo + " is not managed by current database");

		return record.getIdentity();
	}

	public OUser getUser() {
		return underlying.getUser();
	}

	public OMetadata getMetadata() {
		return underlying.getMetadata();
	}

	/**
	 * Returns a wrapped OCommandRequest instance to catch the result-set by converting it before to return to the user application.
	 */
	public  RET command(final OCommandRequest iCommand) {
		return (RET) new OCommandSQLPojoWrapper(this, underlying.command(iCommand));
	}

	public > RET query(final OQuery iCommand, final Object... iArgs) {
		checkOpeness();

		convertParameters(iArgs);

		final List result = underlying.query(iCommand, iArgs);

		if (result == null)
			return null;

		final List resultPojo = new ArrayList();
		Object obj;
		for (OIdentifiable doc : result) {
			if (doc instanceof ODocument) {
				// GET THE ASSOCIATED DOCUMENT
				if (((ODocument) doc).getClassName() == null)
					obj = doc;
				else
					obj = getUserObjectByRecord(((ODocument) doc), iCommand.getFetchPlan(), true);

				resultPojo.add(obj);
			} else {
				resultPojo.add(doc);
			}

		}

		return (RET) resultPojo;
	}

	public ODatabaseComplex delete(final ORecordInternal iRecord) {
		underlying.delete((ODocument) iRecord);
		return this;
	}

	public ODatabaseComplex delete(final ORID iRID) {
		underlying.delete(iRID);
		return this;
	}

	public > DBTYPE registerHook(final ORecordHook iHookImpl) {
		underlying.registerHook(iHookImpl);
		return (DBTYPE) this;
	}

	public boolean callbackHooks(final TYPE iType, final OIdentifiable iObject) {
		return underlying.callbackHooks(iType, iObject);
	}

	public Set getHooks() {
		return underlying.getHooks();
	}

	public > DBTYPE unregisterHook(final ORecordHook iHookImpl) {
		underlying.unregisterHook(iHookImpl);
		return (DBTYPE) this;
	}

	public boolean isMVCC() {
		return underlying.isMVCC();
	}

	public > DBTYPE setMVCC(final boolean iMvcc) {
		underlying.setMVCC(iMvcc);
		return (DBTYPE) this;
	}

	/**
	 * Specifies if retain handled objects in memory or not. Setting it to false can improve performance on large inserts. Default is
	 * enabled.
	 * 
	 * @param iValue
	 *          True to enable, false to disable it.
	 * @see #isRetainObjects()
	 */
	public ODatabasePojoAbstract setRetainObjects(final boolean iValue) {
		retainObjects = iValue;
		return this;
	}

	/**
	 * Returns true if current configuration retains objects, otherwise false
	 * 
	 * @param iValue
	 *          True to enable, false to disable it.
	 * @see #setRetainObjects(boolean)
	 */

	public boolean isRetainObjects() {
		return retainObjects;
	}

	public ODocument getRecordByUserObject(final Object iPojo, final boolean iCreateIfNotAvailable) {
		if (iPojo instanceof ODocument)
			return (ODocument) iPojo;
		else if (iPojo instanceof Proxy)
			return ((OObjectProxyMethodHandler) ((ProxyObject) iPojo).getHandler()).getDoc();

		ODocument record = objects2Records.get(iPojo);
		if (record == null) {
			// SEARCH BY RID
			final ORID rid = OObjectSerializerHelper.getObjectID(this, iPojo);
			if (rid != null && rid.isValid()) {
				record = rid2Records.get(rid);
				if (record == null)
					// LOAD IT
					record = underlying.load(rid);
			} else if (iCreateIfNotAvailable) {
				record = underlying.newInstance(iPojo.getClass().getSimpleName());
			} else {
				return null;
			}

			registerUserObject(iPojo, record);
		}

		return record;
	}

	public boolean existsUserObjectByRID(ORID iRID) {
		return rid2Records.containsKey(iRID);
	}

	public ODocument getRecordById(final ORID iRecordId) {
		return iRecordId.isValid() ? rid2Records.get(iRecordId) : null;
	}

	public boolean isManaged(final Object iEntity) {
		return objects2Records.containsKey(iEntity);
	}

	public T getUserObjectByRecord(final OIdentifiable iRecord, final String iFetchPlan) {
		return getUserObjectByRecord(iRecord, iFetchPlan, true);
	}

	public T getUserObjectByRecord(final OIdentifiable iRecord, final String iFetchPlan, final boolean iCreate) {
		if (!(iRecord instanceof ODocument))
			return null;

		// PASS FOR rid2Records MAP BECAUSE IDENTITY COULD BE CHANGED IF WAS NEW AND IN TX
		ODocument record = rid2Records.get(iRecord.getIdentity());

		if (record == null)
			record = (ODocument) iRecord;

		Object pojo = records2Objects.get(record);

		if (pojo == null && iCreate) {
			checkOpeness();

			try {
				if (iRecord.getRecord().getInternalStatus() == ORecordElement.STATUS.NOT_LOADED)
					record = (ODocument) record.load();

				pojo = newInstance(record.getClassName());
				registerUserObject(pojo, record);

				stream2pojo(record, pojo, iFetchPlan);

			} catch (Exception e) {
				throw new OConfigurationException("Cannot retrieve pojo from record " + record, e);
			}
		}

		return (T) pojo;
	}

	public void attach(final Object iPojo) {
		checkOpeness();

		final ODocument record = objects2Records.get(iPojo);
		if (record != null)
			return;

		if (OObjectSerializerHelper.hasObjectID(iPojo)) {
		} else {
			throw new OObjectNotDetachedException("Cannot attach a non-detached object");
		}
	}

	public  RET detach(final Object iPojo) {
		checkOpeness();

		for (Field field : iPojo.getClass().getDeclaredFields()) {
			final Object value = OObjectSerializerHelper.getFieldValue(iPojo, field.getName());
			if (value instanceof OLazyObjectMultivalueElement)
				((OLazyObjectMultivalueElement) value).detach();
		}

		return (RET) iPojo;
	}

	/**
	 * Register a new POJO
	 */
	public void registerUserObject(final Object iObject, final ORecordInternal iRecord) {
		if (!(iRecord instanceof ODocument))
			return;

		final ODocument doc = (ODocument) iRecord;

		if (retainObjects) {
			if (iObject != null) {
				objects2Records.put(iObject, doc);
				records2Objects.put(doc, (T) iObject);

				OObjectSerializerHelper.setObjectID(iRecord.getIdentity(), iObject);
				OObjectSerializerHelper.setObjectVersion(iRecord.getVersion(), iObject);
			}

			final ORID rid = iRecord.getIdentity();
			if (rid.isValid())
				rid2Records.put(rid, doc);
		}
	}

	public void unregisterPojo(final T iObject, final ODocument iRecord) {
		if (iObject != null)
			objects2Records.remove(iObject);

		if (iRecord != null) {
			records2Objects.remove(iRecord);

			final ORID rid = iRecord.getIdentity();
			if (rid.isValid())
				rid2Records.remove(rid);
		}
	}

	protected void clearNewEntriesFromCache() {
		for (Iterator> it = rid2Records.entrySet().iterator(); it.hasNext();) {
			Entry entry = it.next();
			if (entry.getKey().isNew()) {
				it.remove();
			}
		}

		for (Iterator> it = objects2Records.entrySet().iterator(); it.hasNext();) {
			Entry entry = it.next();
			if (entry.getValue().getIdentity().isNew()) {
				it.remove();
			}
		}

		for (Iterator> it = records2Objects.entrySet().iterator(); it.hasNext();) {
			Entry entry = it.next();
			if (entry.getKey().getIdentity().isNew()) {
				it.remove();
			}
		}
	}

	/**
	 * Converts an array of parameters: if a POJO is used, then replace it with its record id.
	 * 
	 * @param iArgs
	 *          Array of parameters as Object
	 * @see #convertParameter(Object)
	 */
	protected void convertParameters(final Object... iArgs) {
		if (iArgs == null)
			return;

		// FILTER PARAMETERS
		for (int i = 0; i < iArgs.length; ++i)
			iArgs[i] = convertParameter(iArgs[i]);
	}

	/**
	 * Convert a parameter: if a POJO is used, then replace it with its record id.
	 * 
	 * @param iParameter
	 *          Parameter to convert, if applicable
	 * @see #convertParameters(Object...)
	 */
	protected Object convertParameter(final Object iParameter) {
		if (iParameter != null)
			// FILTER PARAMETERS
			if (iParameter instanceof Map) {
				Map map = (Map) iParameter;

				for (Entry e : map.entrySet()) {
					map.put(e.getKey(), convertParameter(e.getValue()));
				}

			} else if (iParameter != null && !OType.isSimpleType(iParameter)) {
				final ORID rid = getIdentity(iParameter);
				if (rid != null && rid.isValid())
					// REPLACE OBJECT INSTANCE WITH ITS RECORD ID
					return rid;
			}

		return iParameter;
	}
}