/*
* 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.3 $
* $Date: 2007-10-22 01:50:38 $
* $Author: scytacki $
*
* Licence Information
* Copyright 2004 The Concord Consortium
*/
package org.concord.otrunk.overlay;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import org.concord.framework.otrunk.OTID;
import org.concord.framework.otrunk.OTPackage;
import org.concord.otrunk.datamodel.BlobResource;
import org.concord.otrunk.datamodel.OTDataObject;
import org.concord.otrunk.datamodel.OTDataObjectFinder;
import org.concord.otrunk.datamodel.OTDataObjectType;
import org.concord.otrunk.datamodel.OTDataPropertyReference;
import org.concord.otrunk.datamodel.OTDatabase;
import org.concord.otrunk.datamodel.OTTransientMapID;
import org.concord.otrunk.datamodel.OTUUID;
import org.concord.otrunk.xml.XMLDataObject;
/**
* OTTemplateDatabase
* Class name and description
*
* Date created: Apr 21, 2005
*
* @author scott<p>
*
*/
public class CompositeDatabase
implements OTDatabase
{
/**
* This is a map from the global id of the base object to the compositeDataObject
* The base object could be in the root database or it could be an object created
* in the overlay
*/
protected HashMap<OTID, CompositeDataObject> dataObjectMap =
new HashMap<OTID, CompositeDataObject>();
OTDataObjectFinder objectFinder;
OTDatabase activeOverlayDb;
Overlay activeOverlay;
ArrayList<Overlay> middleOverlays;
OTID databaseId;
private OverlayListener overlayListener = new OverlayListener(){
public void newDeltaObject(Overlay overlay, OTDataObject baseObject)
{
// see if we have a this baseObject in a our list, update its
// middleObjects if we do
CompositeDataObject compDataObject = dataObjectMap.get(baseObject.getGlobalId());
if(compDataObject == null) {
return;
}
// reconstruct the middle deltas
OTDataObject[] middleDeltas = createMiddleDeltas(baseObject);
compDataObject.setMiddleDeltas(middleDeltas);
return;
}
};
public CompositeDatabase(OTDataObjectFinder objectFinder, Overlay activeOverlay)
{
this.objectFinder = objectFinder;
this.activeOverlayDb = activeOverlay.getOverlayDatabase();
this.activeOverlay = activeOverlay;
databaseId = OTUUID.createOTUUID();
}
public void setOverlays(ArrayList<Overlay> overlays)
{
this.middleOverlays = overlays;
for (Overlay overlay : overlays) {
overlay.addOverlayListener(overlayListener);
}
}
public OTID getDatabaseId()
{
return databaseId;
}
/* (non-Javadoc)
* @see org.concord.otrunk.datamodel.OTDatabase#setRoot(org.concord.framework.otrunk.OTID)
*/
public void setRoot(OTID rootId) throws Exception
{
// Do nothing
}
/* (non-Javadoc)
* @see org.concord.otrunk.datamodel.OTDatabase#getRoot()
*/
public OTDataObject getRoot() throws Exception
{
// Do nothing
return null;
}
/* (non-Javadoc)
* @see org.concord.otrunk.datamodel.OTDatabase#createDataObject()
*/
public OTDataObject createDataObject(OTDataObjectType type) throws Exception
{
// in this case we need to create a new state object and wrap it
OTDataObject childObject = activeOverlayDb.createDataObject(type);
CompositeDataObject compositeDataObject =
new CompositeDataObject(childObject, this, null, false);
activeOverlay.registerNonDeltaObject(childObject);
//System.out.println("v3. " + userDataObject.getGlobalId());
return compositeDataObject;
}
/* (non-Javadoc)
* @see org.concord.otrunk.datamodel.OTDatabase#createDataObject(org.concord.framework.otrunk.OTID)
*/
public OTDataObject createDataObject(OTDataObjectType type, OTID id) throws Exception
{
// in this case we need to create a new state object and wrap it
OTDataObject childObject = activeOverlayDb.createDataObject(type, id);
CompositeDataObject userDataObject =
new CompositeDataObject(childObject, this, null, false);
activeOverlay.registerNonDeltaObject(childObject);
//System.out.println("v3. " + userDataObject.getGlobalId());
return userDataObject;
}
public OTDataObject getActiveDeltaObject(OTDataObject baseObject)
{
OTDataObject deltaObject = activeOverlay.getDeltaObject(baseObject);
if(deltaObject instanceof XMLDataObject){
((XMLDataObject) deltaObject).setSaveNulls(true);
}
return deltaObject;
}
public OTDataObject createActiveDeltaObject(OTDataObject baseObject)
{
OTDataObject deltaObject = activeOverlay.createDeltaObject(baseObject);
if(deltaObject instanceof XMLDataObject){
((XMLDataObject) deltaObject).setSaveNulls(true);
}
return deltaObject;
}
public OTDatabase getActiveOverlayDb() {
return activeOverlayDb;
}
/**
* FIXME: This method uses the state database to determine whether
* an object can be directly changed or it needs to be wrapped by
* a template (user object) and just that object is changed.
* This should not be dependent on the state database because in some
* cases there will not be a separate database just for the state
* objects. So in this case there should be another way to know if
* an object should be directly changed when it is access through
* this database. The reference map should probably be used for
* this.
*
* @see org.concord.otrunk.datamodel.OTDatabase#getOTDataObject(org.concord.otrunk.datamodel.OTDataObject, org.concord.framework.otrunk.OTID)
*/
public OTDataObject getOTDataObject(OTDataObject dataParent, OTID childId)
throws Exception
{
childId = resolveID(childId);
CompositeDataObject userDataObject = dataObjectMap.get(childId);
if(userDataObject != null) {
//System.out.println("v1. " + userDataObject.getGlobalId());
return userDataObject;
}
if(activeOverlay.contains(childId)) {
// the requested object is part of the overlay.
// this object might have references. so we need to
// wrap it so the returned data object has us as
// the database
OTDataObject childObject = activeOverlayDb.getOTDataObject(null, childId);
userDataObject = new CompositeDataObject(childObject, this, null, false);
//System.out.println("v3. " + userDataObject.getGlobalId());
// save this object so if it is referenced again the same
// dataobject is returned.
dataObjectMap.put(childId, userDataObject);
return userDataObject;
}
// this object isn't in the creationDb and we haven't accessed
// it before so we need to make a new one
// We use the OTDataObjectFinder object for this. The code that setup
// this composite database should pass in a objectFinder which only
// finds objects this composite database should compose.
OTDataObject baseObject = objectFinder.findDataObject(childId);
if(baseObject == null) {
// we are in a bad state here. there was a request for a child
// object that we can't find. Instead of making a bogus user
// object lets throw a runtime exception
System.err.println("can't find user object: " + childId);
return null;
//throw new RuntimeException("can't find user object: " + childId);
}
OTDataObject[] middleDeltas = createMiddleDeltas(baseObject);
userDataObject = new CompositeDataObject(baseObject, this, middleDeltas, true);
dataObjectMap.put(childId, userDataObject);
// System.err.println("created userDataObject template-id: " + childId +
// " userDataObject-id: " + userDataObject.getGlobalId());
//System.out.println("v5. " + userDataObject.getGlobalId());
return userDataObject;
}
private OTDataObject[] createMiddleDeltas(OTDataObject baseObject)
{
OTDataObject middleDeltas [] = null;
if(middleOverlays != null){
ArrayList<OTDataObject> middleDeltasList = new ArrayList<OTDataObject>();
// if we have middle overlays then we need to see if any of them have a delta for this
// object
for(int i=0; i<middleOverlays.size(); i++){
Overlay middleOverlay = middleOverlays.get(i);
OTDataObject middleDelta = middleOverlay.getDeltaObject(baseObject);
if(middleDelta != null){
middleDeltasList.add(middleDelta);
}
}
if(middleDeltasList.size() > 0){
middleDeltas = new OTDataObject[middleDeltasList.size()];
middleDeltasList.toArray(middleDeltas);
}
}
return middleDeltas;
}
public OTID resolveID(OTID id)
{
if(id instanceof OTTransientMapID) {
OTTransientMapID mappedID = (OTTransientMapID)id;
if(getDatabaseId().equals(mappedID.getMapToken())) {
// This is an id that came from our database
// so get the relative part of the id which is
// either an id in the
// authored database or an id of a brand new object in
// the user database
return mappedID.getMappedId();
} else {
// this transient id is not in our database. So probably this is a case where
// multiple overlays are being used. So this object is coming from another
// composite database. Currently this isn't handled. So this throws an exception
// to make it a little easier to track down the source of the problem.
OTID underlyingId = mappedID.getMappedId();
OTDataObject dataObject = null;
try {
dataObject = objectFinder.findDataObject(underlyingId);
} catch (Exception e) {
e.printStackTrace();
}
URI dbURI = null;
if(dataObject != null){
OTDatabase database = dataObject.getDatabase();
dbURI = database.getURI();
}
throw new RuntimeException("Can't resolve id: " + mappedID.toInternalForm() +
"\n whose underlying object is from database with uri: " + dbURI);
}
}
return id;
}
/* (non-Javadoc)
* @see org.concord.otrunk.datamodel.OTDatabase#contains(org.concord.framework.otrunk.OTID)
*/
public boolean contains(OTID id)
{
// we only contain one id which is our database id
// this id will be used by OTUserDataObject in their
// globalIds
return id.equals(databaseId);
}
/* (non-Javadoc)
* @see org.concord.otrunk.datamodel.OTDatabase#close()
*/
public void close()
{
// do nothing
}
/* (non-Javadoc)
* @see org.concord.otrunk.datamodel.OTDatabase#createBlobResource(java.net.URL)
*/
public BlobResource createBlobResource(URL url)
{
return new BlobResource(url);
}
/* (non-Javadoc)
* @see org.concord.otrunk.datamodel.OTDatabase#getPackageClasses()
*/
public ArrayList<Class<? extends OTPackage>> getPackageClasses()
{
// TODO Auto-generated method stub
return null;
}
public URI getURI()
{
try {
return new URI("composite-db:/" + getDatabaseId());
} catch (URISyntaxException e) {
e.printStackTrace();
}
return null;
}
public HashMap<OTID, CompositeDataObject> getDataObjects()
{
return dataObjectMap;
}
public ArrayList<OTDataPropertyReference> getOutgoingReferences(OTID otid)
{
return activeOverlayDb.getOutgoingReferences(otid);
}
public ArrayList<OTDataPropertyReference> getIncomingReferences(OTID otid)
{
return activeOverlayDb.getIncomingReferences(otid);
}
public synchronized void pruneNonDeltaObjects() {
activeOverlay.pruneNonDeltaObjects();
}
public void recordReference(OTID parentID, OTID childID, String property)
{
activeOverlayDb.recordReference(parentID, childID, property);
}
public void recordReference(OTDataObject parent, OTDataObject child, String property)
{
activeOverlayDb.recordReference(parent, child, property);
}
public void removeReference(OTDataObject parent, OTDataObject child)
{
activeOverlayDb.removeReference(parent, child);
}
public void removeReference(OTID parentID, OTID childID)
{
activeOverlayDb.removeReference(parentID, childID);
}
}