/*
* Copyright (C) 2004 The Concord Consortium, Inc.,
* 10 Concord Crossing, Concord, MA 01742
*
* Web Site: http://www.concord.org
* Email: info@concord.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* END LICENSE */
/*
* Last modification information:
* $Revision: 1.27 $
* $Date: 2007-10-22 01:50:37 $
* $Author: scytacki $
*
* Licence Information
* Copyright 2004 The Concord Consortium
*/
package org.concord.otrunk;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.concord.framework.otrunk.OTControllerRegistry;
import org.concord.framework.otrunk.OTControllerService;
import org.concord.framework.otrunk.OTID;
import org.concord.framework.otrunk.OTObject;
import org.concord.framework.otrunk.OTObjectList;
import org.concord.framework.otrunk.OTObjectService;
import org.concord.framework.otrunk.OTPackage;
import org.concord.framework.otrunk.OTResourceSchema;
import org.concord.framework.otrunk.otcore.OTClass;
import org.concord.otrunk.asm.GeneratedClassLoader;
import org.concord.otrunk.datamodel.DataObjectUtil;
import org.concord.otrunk.datamodel.OTDataList;
import org.concord.otrunk.datamodel.OTDataObject;
import org.concord.otrunk.datamodel.OTDataObjectType;
import org.concord.otrunk.datamodel.OTDatabase;
import org.concord.otrunk.datamodel.OTExternalIDProvider;
import org.concord.otrunk.datamodel.OTTransientMapID;
import org.concord.otrunk.datamodel.OTUUID;
import org.concord.otrunk.otcore.impl.ReflectiveOTClassFactory;
import org.concord.otrunk.overlay.CompositeDatabase;
import org.concord.otrunk.view.OTConfig;
import org.concord.otrunk.xml.XMLDataObject;
public class OTObjectServiceImpl
implements OTObjectService, OTExternalIDProvider
{
public final static Logger logger =
Logger.getLogger(OTObjectServiceImpl.class.getCanonicalName());
protected OTrunkImpl otrunk;
protected OTDatabase creationDb;
protected OTDatabase mainDb;
protected ArrayList<OTObjectServiceListener> listeners =
new ArrayList<OTObjectServiceListener>();
public OTObjectServiceImpl(OTrunkImpl otrunk)
{
this.otrunk = otrunk;
}
public void setCreationDb(OTDatabase creationDb)
{
this.creationDb = creationDb;
}
public OTDatabase getCreationDb()
{
return creationDb;
}
public void setMainDb(OTDatabase mainDb)
{
this.mainDb = mainDb;
}
public OTDatabase getMainDb()
{
return mainDb;
}
public <T extends OTObject> T createObject(Class<T> objectClass)
throws Exception
{
OTObjectInternal otObjectImpl = createOTObjectInternal(objectClass);
T newObject = loadOTObject(otObjectImpl, objectClass);
return newObject;
}
protected OTObjectInternal createOTObjectInternal(
Class<? extends OTObject> objectClass)
throws Exception
{
String className = objectClass.getName();
OTClass otClass = OTrunkImpl.getOTClass(className);
if(otClass == null){
// Can't find existing otClass for this class try to make one
otClass = ReflectiveOTClassFactory.singleton.registerClass(objectClass);
if(otClass == null){
// Java class isn't a valid OTObject
throw new IllegalStateException("Invalid OTClass definition: " + className);
}
// This will add the properties for this new class plus any dependencies that
// were registered at the same time.
ReflectiveOTClassFactory.singleton.processAllNewlyRegisteredClasses();
}
OTDataObjectType type = new OTDataObjectType(objectClass.getName());
OTDataObject dataObject = createDataObject(type);
OTObjectInternal otObjectImpl =
new OTObjectInternal(dataObject, this, otClass);
return otObjectImpl;
}
public OTObject getOTObject(OTID childID) throws Exception
{
return getOTObject(childID, false);
}
@SuppressWarnings("unchecked")
public OTObject getOTObject(OTID childID, boolean reload) throws Exception
{
// sanity check
if(childID == null) {
throw new Exception("Null child id");
}
OTDataObject childDataObject = getOTDataObject(childID);
if(childDataObject == null) {
// we have a null internal object that means the child doesn't
// exist in our database/databases.
//
// This will happen with the aggregate views which display different overlays
// of the same object. Each overlay is going to come from a different objectService
// So if we can't find this object then we go out to OTrunk to see if it can find
// the object. The way that this happens needs to be more clear so the ramifcations
// are clear.
return otrunk.getOrphanOTObject(childID, this, reload);
}
// Look for our object to see it is already setup in the otrunk list of loaded objects
// it might be better to have each object service maintain its own list of loaded objects
OTObject otObject = otrunk.getLoadedObject(childDataObject.getGlobalId(), reload);
if(otObject != null) {
return otObject;
}
String otObjectClassStr = OTrunkImpl.getClassName(childDataObject);
if(otObjectClassStr == null) {
return null;
}
Class<? extends OTObject> otObjectClass =
(Class<? extends OTObject>) Class.forName(otObjectClassStr);
OTObjectInternal otObjectInternal =
new OTObjectInternal(childDataObject, this, OTrunkImpl.getOTClass(otObjectClassStr));
return loadOTObject(otObjectInternal, otObjectClass);
}
public OTID getOTID(String otidStr)
{
return otrunk.getOTID(otidStr);
}
public OTControllerService createControllerService() {
OTControllerRegistry registry =
otrunk.getService(OTControllerRegistry.class);
return new OTControllerServiceImpl(this, registry);
}
// This will work as long as all of the object classes are loaded by the same classloader
// if the OTClass classes are loaded by different classloaders there will probably have
// to be multiple GeneratedClassLoader
GeneratedClassLoader asmClassLoader = null;
private HashMap<String, String> preserveUUIDCallers = new HashMap<String, String>();
public GeneratedClassLoader getASMClassLoader()
{
if(asmClassLoader != null){
return asmClassLoader;
}
asmClassLoader = new GeneratedClassLoader(OTObjectServiceImpl.class.getClassLoader());
return asmClassLoader;
}
@SuppressWarnings("unchecked")
public <T extends OTObject> T loadOTObject(OTObjectInternal otObjectImpl, Class<T> otObjectClass)
throws Exception
{
T otObject = null;
if(otObjectClass.isInterface()) {
if(OTConfig.getBooleanProp(OTConfig.USE_ASM, false)){
Class<? extends AbstractOTObject> generatedClass =
getASMClassLoader().generateClass(otObjectClass, otObjectImpl.otClass());
OTObjectInternal internalObj = generatedClass.newInstance();
otObject = (T)internalObj;
internalObj.setup(otObjectImpl);
internalObj.setEventSource(internalObj);
} else {
OTBasicObjectHandler handler = new OTBasicObjectHandler(otObjectImpl, otrunk, otObjectClass);
try {
otObject = (T)Proxy.newProxyInstance(otObjectClass.getClassLoader(),
new Class[] { otObjectClass }, handler);
handler.setOTObject(otObject);
} catch (ClassCastException e){
throw new RuntimeException("The OTClass: " + otObjectClass +
" does not extend OTObject or OTObjectInterface", e);
}
}
} else if(AbstractOTObject.class.isAssignableFrom(otObjectClass)){
Class<? extends AbstractOTObject> generatedClass =
getASMClassLoader().generateClass(otObjectClass, otObjectImpl.otClass());
OTObjectInternal internalObj = generatedClass.newInstance();
otObject = (T)internalObj;
internalObj.setup(otObjectImpl);
internalObj.setEventSource(internalObj);
} else {
otObject = setResourcesFromSchema(otObjectImpl, otObjectClass);
}
notifyLoaded(otObject);
otObject.init();
otrunk.putLoadedObject(otObject, otObjectImpl.getGlobalId());
return otObject;
}
/**
* @param otObject
*/
protected void notifyLoaded(OTObject otObject)
{
for(int i=0; i < listeners.size(); i++) {
(listeners.get(i)).objectLoaded(otObject);
}
}
/**
* Track down the objects schema by looking at the type
* of class of the argument to setResources method
*
* @param dataObject
* @param otObject
*/
@SuppressWarnings("unchecked")
public <T extends OTObject> T setResourcesFromSchema(OTObjectInternal otObjectImpl, Class<T> otObjectClass)
{
Constructor<T> [] memberConstructors = (Constructor<T> [])otObjectClass.getConstructors();
Constructor<T> resourceConstructor = memberConstructors[0];
Class<?> [] params = resourceConstructor.getParameterTypes();
if(memberConstructors.length > 1) {
System.err.println("OTObjects should only have 1 constructor");
return null;
}
if(params == null | params.length == 0) {
try {
return otObjectClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
OTResourceSchemaHandler handler = null;
Object constructorParams [] = new Object [params.length];
int nextParam = 0;
if(params[0].isInterface() &&
OTResourceSchema.class.isAssignableFrom(params[0])){
Class<? extends OTResourceSchema> schemaClass = (Class<? extends OTResourceSchema>)params[0];
handler = new OTResourceSchemaHandler(otObjectImpl, otrunk,
schemaClass);
Class<?> [] interfaceList = new Class[] { schemaClass };
Object resources =
Proxy.newProxyInstance(schemaClass.getClassLoader(),
interfaceList, handler);
constructorParams[0] = resources;
nextParam++;
}
for(int i=nextParam; i<params.length; i++) {
// look for a service in the services list to can
// be used for this param
constructorParams[i] = otrunk.getService(params[i]);
if(constructorParams[i] == null) {
System.err.println("No service could be found to handle the\n" +
" requirement of: " + otObjectClass + "\n" +
" for: " + params[i]);
return null;
}
}
T otObject = null;
try {
otObject = resourceConstructor.newInstance(constructorParams);
// now we need to pass the otObject to the schema handler so it can
// set that as the source of OTChangeEvents
handler.setEventSource(otObject);
} catch (Exception e) {
e.printStackTrace();
return null;
}
return otObject;
}
boolean managesObject(OTID id)
{
if(id instanceof OTTransientMapID) {
Object mapToken = ((OTTransientMapID)id).getMapToken();
if (creationDb.getDatabaseId() == null) {
System.err.println("Database with a null id!");
return false;
}
return creationDb.getDatabaseId().equals(mapToken);
}
// Check our mainDb to see if it contains the object
// FIXME there is an issue here about what the difference between the
// mainDb and the creationDb.
return mainDb.contains(id);
}
private OTDataObject createDataObject(OTDataObjectType type)
throws Exception
{
return creationDb.createDataObject(type);
}
/**
*
* @param dataParent
* @param childID
* @return
* @throws Exception
*/
OTDataObject getOTDataObject(OTID childID)
throws Exception
{
// sanity check
if(childID == null) {
throw new Exception("Null child Id");
}
OTDataObject childDataObject = mainDb.getOTDataObject(null, childID);
return childDataObject;
}
/* (non-Javadoc)
* @see org.concord.framework.otrunk.OTObjectService#copyObject(org.concord.framework.otrunk.OTObject, int)
*/
public OTObject copyObject(OTObject original, int maxDepth)
throws Exception
{
OTObjectList orphanObjectList = null;
OTDataObject rootDO = otrunk.getRootDataObject();
OTObject root = getOTObject(rootDO.getGlobalId());
if(root instanceof OTSystem) {
orphanObjectList = ((OTSystem)root).getLibrary();
}
return copyObject(original, orphanObjectList, maxDepth);
}
public OTObject copyObject(OTObject original, OTObjectList orphanObjectList,
int maxDepth)
throws Exception
{
// make a copy of the original objects data object
// it is easier to copy data objects than the actual objects
OTDataObject originalDataObject = getOTDataObject(original);
// Assume the object list is our object list impl
OTDataList orphanDataList =
((OTObjectListImpl)orphanObjectList).getDataList();
OTDataObject copyDataObject =
DataObjectUtil.copy(originalDataObject, creationDb,
orphanDataList, maxDepth, this, otrunk.getDataObjectFinder(), false);
return getOTObject(copyDataObject.getGlobalId());
}
public void copyInto(OTObject source, OTObject destination, int maxDepth, boolean onlyModifications) throws Exception {
OTObjectList orphanObjectList = null;
OTDataObject rootDO = otrunk.getRootDataObject();
OTObject root = getOTObject(rootDO.getGlobalId());
if(root instanceof OTSystem) {
orphanObjectList = ((OTSystem)root).getLibrary();
}
// make a copy of the original objects data object
// it is easier to copy data objects than the actual objects
OTDataObject sourceDO = getOTDataObject(source);
OTDataObject destDO = getOTDataObject(destination);
// Assume the object list is our object list impl
OTDataList orphanDataList = null;
if (orphanObjectList != null) {
orphanDataList = ((OTObjectListImpl)orphanObjectList).getDataList();
}
// OTDataObject copyDataObject = DataObjectUtil.copy(originalDataObject, creationDb, orphanDataList, maxDepth, this, otrunk.getDataObjectFinder());
ArrayList<OTObjectService> idProviders = new ArrayList<OTObjectService>();
idProviders.add(source.getOTObjectService());
idProviders.add(destination.getOTObjectService());
DataObjectUtil.copyInto(sourceDO, destDO, orphanDataList, maxDepth, this, otrunk.getDataObjectFinder(), onlyModifications);
}
public void addObjectServiceListener(OTObjectServiceListener listener)
{
if(listeners.contains(listener)) {
return;
}
listeners.add(listener);
}
public void removeObjectServiceListener(OTObjectServiceListener listener)
{
listeners.remove(listener);
}
/* (non-Javadoc)
* @see org.concord.framework.otrunk.OTObjectService#registerPackageClass(java.lang.Class)
*/
public void registerPackageClass(Class<? extends OTPackage> packageClass)
{
otrunk.registerPackageClass(packageClass);
}
/* (non-Javadoc)
* @see org.concord.framework.otrunk.OTObjectService#getOTrunkService(java.lang.Class)
*/
public <T> T getOTrunkService(Class<T> serviceInterface)
{
return otrunk.getService(serviceInterface);
}
public String getExternalID(OTObject object)
{
OTID globalId = object.getGlobalId();
return getExternalID(globalId);
}
public String getExternalID(OTID otid)
{
if(mainDb instanceof CompositeDatabase){
return ((CompositeDatabase)mainDb).resolveID(otid).toExternalForm();
}
if(otid instanceof OTTransientMapID){
throw new RuntimeException("Cannot get an external id for " + otid +
" using this object service with mainDb: " + mainDb);
}
return otid.toExternalForm();
}
static OTDataObject getOTDataObject(OTObject otObject)
{
if(otObject instanceof OTObjectInternal){
return ((OTObjectInternal)otObject).dataObject;
}
return OTInvocationHandler.getOTDataObject(otObject);
}
public URL getCodebase(OTObject otObject)
{
try {
OTDataObject dataObject = getOTDataObject(otObject);
return dataObject.getCodebase();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void preserveUUID(OTObject otObject)
{
OTID id = otObject.getGlobalId();
if(!(id instanceof OTUUID)){
logPreserveUUIDError("object does not have a UUID " + otObject);
return;
}
try {
OTDataObject dataObject = getOTDataObject(id);
if(!(dataObject instanceof XMLDataObject)){
logPreserveUUIDError("object is not backed by a XMLDatabase " + otObject);
return;
}
((XMLDataObject)dataObject).setPreserveUUID(true);
} catch (Exception e) {
e.printStackTrace();
}
}
private void logPreserveUUIDError(String string)
{
logger.warning(string);
Throwable throwable =
new IllegalArgumentException(string);
StackTraceElement stackTraceElement = throwable.getStackTrace()[2];
String callerStr = stackTraceElement.getClassName() + "." +
stackTraceElement.getMethodName();
Object value = preserveUUIDCallers.get(callerStr);
if(value != null){
return;
}
preserveUUIDCallers.put(callerStr, callerStr);
logger.log(Level.FINE, "first call from method which caused bad preserveUUID call",
throwable);
}
}