/*
* 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 */
package org.concord.otrunk;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.WeakHashMap;
import java.util.Map.Entry;
import org.concord.framework.otrunk.OTController;
import org.concord.framework.otrunk.OTControllerRegistry;
import org.concord.framework.otrunk.OTControllerService;
import org.concord.framework.otrunk.OTObject;
import org.concord.framework.otrunk.OTObjectList;
import org.concord.framework.otrunk.OTObjectService;
public class OTControllerServiceImpl implements OTControllerService
{
/**
* key is a OTObject
* value is a realObject
*/
Map<OTObject, WeakReference<Object>> realObjectFromOTMap =
new HashMap<OTObject, WeakReference<Object>>();
/**
* key is the real object
* value is the OTController
*/
Map<Object, OTController> controllerFromRealMap = new WeakHashMap<Object, OTController>();
/**
* key is the OTObject
* value is the OTController
*/
Map<OTObject, OTController> controllerFromOTMap = new WeakHashMap<OTObject, OTController>();
/**
* A map of services that can be used by controller.
*/
Map<Class<?>, Object> serviceMap = new HashMap<Class<?>, Object>();
OTObjectService objectService;
OTControllerRegistry registry;
OTControllerServiceImpl sharedService;
// Track if we are disposing
private boolean disposing = false;
public OTControllerServiceImpl(OTObjectService objectService,
OTControllerRegistry registry)
{
this.objectService = objectService;
this.registry = registry;
}
/**
* @see OTControllerService.getRealObject
*/
public Object getRealObject(OTObject otObject)
{
// Handle the trivial case.
if(otObject == null) {
return null;
}
Object realObject = getExistingRealObjectFromOTObject(otObject);
if(realObject != null){
return realObject;
}
// if we are in the middle of disposing and we don't already have a real object just return null
if(disposing){
return null;
}
realObject = setupRealObject(otObject, null);
return realObject;
}
/**
* A helper method to get a Vector of real objects from an
* OTObjectList, so that we don't have to first get the OTObjectList
* and then create all the RealObjects ourselves.
*
* @param otObjectList
* @return
*/
public Vector<Object> getRealObjects(OTObjectList otObjectList){
Vector<OTObject> otVector = otObjectList.getVector();
Vector<Object> objVector = new Vector<Object>();
for (OTObject otObject : otVector) {
objVector.add(getRealObject(otObject));
}
return objVector;
}
private OTController getExistingControllerFromOTObject(OTObject otObject)
{
// check to see if we already have a controller for this otObject
OTController controller = (OTController)controllerFromOTMap.get(otObject);
if (controller != null) {
return controller;
}
// check to see if there is already shared controller for this otObject
if(sharedService != null){
return sharedService.getExistingControllerFromOTObject(otObject);
}
return null;
}
private OTController getExistingControllerFromRealObject(Object realObject)
{
// check if we already have a controller, if so then return its otObject
OTController controller;
synchronized(controllerFromRealMap){
controller = (OTController)controllerFromRealMap.get(realObject);
}
if(controller != null) {
return controller;
}
// check to see if there is already shared controller for this otObject
if(sharedService != null){
return sharedService.getExistingControllerFromRealObject(realObject);
}
return null;
}
/**
* You should check to see if the controller has already been created before
* calling this method.
*
* @param otObject
* @param realObject
* @return
*/
private final OTController getControllerInternal(OTObject otObject, Object realObject)
{
// need to find the OTController that can handle this OTObject
Class<? extends OTObject> otObjectClass = null;
if(otObject != null) {
otObjectClass = otObject.getClass();
}
Class<?> realObjectClass = null;
if(realObject != null) {
realObjectClass = realObject.getClass();
}
OTController controller = createControllerInternal(otObjectClass, realObjectClass);
return controller;
}
private OTController getAndInitializeController(OTObject otObject, Object realObject)
{
// check to see if we already have a controller for this otObject
OTController controller = getExistingControllerFromOTObject(otObject);
if (controller != null) {
// we already have a controller for this otObject
// we don't need to initialize it because the only way we could have found an
// existing controller is if it was already initialized.
return controller;
} else {
controller = getControllerInternal(otObject, realObject);
}
if(sharedService != null && controller.isRealObjectSharable(otObject, realObject)){
controller.initialize(otObject, sharedService);
} else {
controller.initialize(otObject, this);
}
return controller;
}
public OTController getController(OTObject otObject)
{
OTController controller = getExistingControllerFromOTObject(otObject);
if (controller != null) {
// we already have a controller for this otObject, so we can skip the
// initialization performed below
return controller;
}
controller = getControllerInternal(otObject, null);
if(sharedService != null && controller.isRealObjectSharable(otObject, null)){
sharedService.initializeAndStoreRelationships(controller, otObject, null);
} else {
initializeAndStoreRelationships(controller, otObject, null);
}
return controller;
}
@SuppressWarnings("unchecked")
private final OTController createControllerInternal(Class<? extends OTObject> otObjectClass,
Class<?> realObjectClass)
{
// need to find the OTController that can handle this OTObject
// need to go through all the parents of the otObjectClass
Class<? extends OTController> controllerClass =
registry.getControllerClassByOTObjectClass(otObjectClass);
// try the first level of interfaces if the class can't be found
// this should really go up the inheritance tree, both interfaces and
// superclasses
// just doing the first level will handle the proxy classes
Class<?> [] interfaces = null;
if(controllerClass == null) {
// I believe this will return only the first level of interfaces
interfaces = otObjectClass.getInterfaces();
for (Class<?> class1 : interfaces) {
// skip non OTObject classes
if(!OTObject.class.isAssignableFrom(class1)){
continue;
}
controllerClass =
registry.getControllerClassByOTObjectClass((Class<? extends OTObject>) class1);
if(controllerClass != null){
break;
}
}
}
if(controllerClass == null){
// Can't find a controller for this otObject
String otObjectClassStr = "" + otObjectClass;
if(Proxy.isProxyClass(otObjectClass) &&
interfaces != null && interfaces.length > 0){
otObjectClassStr = "" + interfaces[0];
}
throw new RuntimeException("Can't find a controller for this otObject: " +
otObjectClassStr);
}
try {
OTController controller = (OTController)controllerClass.newInstance();
return controller;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
private Object getExistingRealObjectFromOTObject(OTObject otObject)
{
Reference<?> ref = (Reference<?>)realObjectFromOTMap.get(otObject);
if(ref != null){
return ref.get();
}
if(sharedService != null){
return sharedService.getExistingRealObjectFromOTObject(otObject);
}
return null;
}
/**
*
* @param controller
* @param otObject
* @param realObject
*/
private final Object setupRealObject(OTObject otObject, Object realObject)
{
// check to see if we already have a controller for this otObject
OTController controller = getExistingControllerFromOTObject(otObject);
if (controller != null) {
// we already have a controller for this otObject
// we might not need to do the following initialization but that is how it was
// working before so I'm going to leave it that way for now.
} else {
controller = getControllerInternal(otObject, realObject);
}
/*
* The relationships between the controller, ot and real object should be stored
* before calling this method, to prevent infinite loops. These loops will happen
* if a controller calls methods on the controller service in loadRealObject or
* registerRealObject and there are circular references. Having circular references
* and calling methods on the controller service is allowed and should be supported.
*/
if(sharedService != null && controller.isRealObjectSharable(otObject, realObject)){
realObject = sharedService.initializeAndStoreRelationships(controller, otObject, realObject);
} else {
realObject = initializeAndStoreRelationships(controller, otObject, realObject);
}
controller.loadRealObject(realObject);
controller.registerRealObject(realObject);
return realObject;
}
public Object getRealObject(OTObject otObject, Object realObject)
{
Object oldRealObject = getExistingRealObjectFromOTObject(otObject);
if(oldRealObject != null){
// TODO what should we do here?
if(oldRealObject == realObject){
System.err.println("otObject already had the object");
} else {
System.err.println("otObject already had a different object");
}
}
if(otObject == null){
String realObjectDesc = "null";
if(realObject != null){
realObjectDesc = "" + realObject.getClass() + ": " + realObject;
}
throw new IllegalArgumentException("null otObject for: " +
realObjectDesc);
}
realObject = setupRealObject(otObject, realObject);
return realObject;
}
public Class<?> [] getRealObjectClasses(Class<? extends OTController> controllerClass) {
try {
Field field = controllerClass.getField("realObjectClasses");
return (Class [])field.get(null);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
@SuppressWarnings("unchecked")
public Class<? extends OTObject> getOTObjectClass(Class<? extends OTController> controllerClass) {
try {
Field field = controllerClass.getField("otObjectClass");
return (Class)field.get(null);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
System.err.println("invalid controller class: " + controllerClass);
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
/**
* This should actually receive the class of the OTObject, that will help look up the correct
* OTController. There could be mutiple controllers which handle a particular real object class.
*
* @see org.concord.framework.otrunk.OTControllerService#getOTObject(java.lang.Object)
*/
public OTObject getOTObject(Object realObject)
{
// Handle the Trivial case
if(realObject == null){
return null;
}
// check if we already have a controller, if so then return its otObject
OTController controller = getExistingControllerFromRealObject(realObject);
if(controller != null) {
return controller.getOTObject();
}
// Figure out which otObjectClass we should be using
// this comes from the controller class we found
// TODO this should look for matches up the inheritance tree.
Class<? extends OTController> controllerClass =
registry.getControllerClassByRealObjectClass(realObject.getClass());
if(controllerClass == null) {
System.err.println("can't find controller class for realObject: "
+ realObject.getClass());
return null;
}
Class<? extends OTObject> otObjectClass = getOTObjectClass(controllerClass);
// if we don't have a view then we don't have the otObject
// so we need to make a new one.
OTObject otObject = null;
try {
otObject = (OTObject)objectService.createObject(otObjectClass);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
if(otObject == null) {
System.err.println("can't create registered OTObject class: " +
otObjectClass);
}
// Instantiate the controller
try {
controller = (OTController)controllerClass.newInstance();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
if(sharedService != null && controller.isRealObjectSharable(otObject, realObject)){
sharedService.initializeAndStoreRelationships(controller, otObject, realObject);
} else {
initializeAndStoreRelationships(controller, otObject, realObject);
}
// save the object so the otObject will pickup all the properties
// from the real object
controller.saveRealObject(realObject);
// in this case the realObject is already initialized
controller.registerRealObject(realObject);
return otObject;
}
private Object initializeAndStoreRelationships(OTController controller, OTObject otObject, Object realObject)
{
// check if we are in the middle of disposing I'm not sure what to do here so for now
// just print a stacktrace and keep going
if(disposing){
Exception exception =
new Exception("Initiializing a controller while in the middle of disposing this service");
exception.printStackTrace();
}
// initialize it
controller.initialize(otObject, this);
// check if the real object is null if it is then we need to create one
// using the initizlied controller
if(realObject == null){
realObject = controller.createRealObject();
}
// store the relationship in the proper places
// this should be done before methods on the controller are called
// this should prevent infinite loops if there is a circular reference
realObjectFromOTMap.put(otObject, new WeakReference<Object>(realObject));
synchronized(controllerFromRealMap){
controllerFromRealMap.put(realObject, controller);
}
controllerFromOTMap.put(otObject, controller);
return realObject;
}
/**
* This currently does not store the relationships between the
* controller, otObject, and real object.
* It is not clear if it should or should not.
*
* @see org.concord.framework.otrunk.OTControllerService#saveRealObject(java.lang.Object, org.concord.framework.otrunk.OTObject)
*/
public void saveRealObject(Object realObject, OTObject otObject)
{
OTController controller = getAndInitializeController(otObject, realObject);
controller.saveRealObject(realObject);
}
/**
* This currently does not store the relationships between the
* controller, otObject, and real object.
* It is not clear if it should or should not.
*
* @see org.concord.framework.otrunk.OTControllerService#registerRealObject(java.lang.Object, org.concord.framework.otrunk.OTObject)
*/
public void registerRealObject(Object realObject, OTObject otObject)
{
OTController controller = getAndInitializeController(otObject, realObject);
controller.registerRealObject(realObject);
}
/**
*
* @see org.concord.framework.otrunk.OTControllerService#registerControllerClass(java.lang.Class)
*/
public void registerControllerClass(Class<? extends OTController> viewClass)
{
registry.registerControllerClass(viewClass);
}
/**
* This currently does not store the relationships between the
* controller, otObject, and real object.
* It is not clear if it should or should not.
*
* @see org.concord.framework.otrunk.OTControllerService#loadRealObject(org.concord.framework.otrunk.OTObject, java.lang.Object)
*/
public void loadRealObject(OTObject otObject, Object realObject)
{
OTController controller = getAndInitializeController(otObject, realObject);
controller.loadRealObject(realObject);
}
/**
* @see org.concord.framework.otrunk.OTControllerService#dispose()
*/
public void dispose()
{
disposing = true;
ArrayList<OTController> disposedControllers = new ArrayList<OTController>();
// There are 2 maps that contain references to controllers.
// They should be in sync so we only have to search one
// we won't dispose controllers in our shared service that is its job not ours
synchronized(controllerFromRealMap){
Set<Entry<Object, OTController>> entries = controllerFromRealMap.entrySet();
Iterator<Entry<Object, OTController>> iterator = entries.iterator();
// Copy the entries so if any of the controller.dispose
// calls cause changes to the list a concurrency exception
// wouldn't be thrown.
ArrayList<Entry<Object, OTController>> entriesCopy = new ArrayList<Entry<Object, OTController>>();
while(iterator.hasNext()){
Entry<Object, OTController> entry = (Entry<Object, OTController>) iterator.next();
entriesCopy.add(entry);
}
iterator = entriesCopy.iterator();
while(iterator.hasNext()){
Entry<Object, OTController> entry = iterator.next();
OTController controller = (OTController) entry.getValue();
if(disposedControllers.contains(controller)){
continue;
}
controller.dispose(entry.getKey());
disposedControllers.add(controller);
}
}
}
public OTControllerServiceImpl getSharedService()
{
return sharedService;
}
public void setSharedService(OTControllerServiceImpl sharedService)
{
this.sharedService = sharedService;
}
public OTControllerService createSubControllerService(OTObjectService subObjectService)
{
OTControllerServiceImpl subService =
new OTControllerServiceImpl(subObjectService, registry);
subService.setSharedService(this);
return subService;
}
public void addService(Class<?> serviceClass, Object service)
{
serviceMap.put(serviceClass, service);
}
@SuppressWarnings("unchecked")
public <T> T getService(Class<T> serviceClass)
{
return (T) serviceMap.get(serviceClass);
}
public OTObjectService getObjectService()
{
return objectService;
}
}