/* * 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 */ /* * Created on Aug 16, 2004 * * TODO To change the template for this generated file go to * Window - Preferences - Java - Code Generation - Code and Comments */ package org.concord.otrunk; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.net.Authenticator; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map.Entry; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.HttpsURLConnection; import org.concord.framework.otrunk.OTBundle; import org.concord.framework.otrunk.OTControllerRegistry; 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.OTServiceContext; import org.concord.framework.otrunk.OTUser; import org.concord.framework.otrunk.OTrunk; import org.concord.framework.otrunk.otcore.OTClass; 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.OTIDFactory; import org.concord.otrunk.datamodel.OTTransientMapID; import org.concord.otrunk.net.HTTPRequestException; import org.concord.otrunk.overlay.CompositeDataObject; import org.concord.otrunk.overlay.CompositeDatabase; import org.concord.otrunk.overlay.OTOverlay; import org.concord.otrunk.overlay.OTOverlayGroup; import org.concord.otrunk.overlay.Overlay; import org.concord.otrunk.overlay.OverlayImpl; import org.concord.otrunk.user.OTReferenceMap; import org.concord.otrunk.user.OTUserObject; import org.concord.otrunk.util.ConcordHostnameVerifier; import org.concord.otrunk.view.OTConfig; import org.concord.otrunk.view.OTUserSession; import org.concord.otrunk.view.OTViewer; import org.concord.otrunk.xml.ExporterJDOM; import org.concord.otrunk.xml.XMLDatabase; /** * @author scott * * TODO To change the template for this generated type comment go to * Window - Preferences - Java - Code Generation - Code and Comments */ public class OTrunkImpl implements OTrunk { private static final Logger logger = Logger.getLogger(OTrunkImpl.class.getName()); protected Hashtable<OTID, Reference<OTObject>> loadedObjects = new Hashtable<OTID, Reference<OTObject>>(); protected Hashtable<OTID, CompositeDatabase> compositeDatabases = new Hashtable<OTID, CompositeDatabase>(); protected Hashtable<OTID, OTObjectService> userObjectServices = new Hashtable<OTID, OTObjectService>(); OTServiceContext serviceContext = new OTServiceContextImpl(); protected OTDatabase rootDb; protected OTDatabase systemDb; protected OTObjectServiceImpl rootObjectService; // synchronized since multiple threads might be modifying the list of databases (esp. the otrunk-intrasession code) List<OTDatabase> databases = Collections.synchronizedList(new ArrayList<OTDatabase>()); Vector<OTUser> users = new Vector<OTUser>(); ArrayList<OTObjectServiceImpl> objectServices = new ArrayList<OTObjectServiceImpl>(); private ArrayList<Class<? extends OTPackage>> registeredPackageClasses = new ArrayList<Class<? extends OTPackage>>(); private OTObjectServiceImpl systemObjectService; private static HashMap<String, OTClass> otClassMap = new HashMap<String, OTClass>(); private boolean sailSavingDisabled = false; OTDataObjectFinder dataObjectFinder = new OTDataObjectFinder() { public OTDataObject findDataObject(OTID id) throws Exception { OTDatabase db = getOTDatabase(id); if(db == null){ return null; } return db.getOTDataObject(null, id); } }; public final static String getClassName(OTDataObject dataObject) { OTDataObjectType type = dataObject.getType(); return type.getClassName(); } public OTrunkImpl(OTDatabase db) { this((OTDatabase)null, db, (ArrayList<OTrunkServiceEntry<?>>)null); } public OTrunkImpl(OTDatabase db, ArrayList<OTrunkServiceEntry<?>> services) { this(null, db, services); } @SuppressWarnings("unchecked") public OTrunkImpl(OTDatabase systemDb, OTDatabase db, ArrayList<OTrunkServiceEntry<?>> services) { try { ConcordHostnameVerifier verifier = new ConcordHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(verifier); } catch (Exception e) { logger.warning("Couldn't initialize the Concord HostnameVerifier!"); } try { URL dummyURL = new URL("http://www.concord.org"); URLConnection openConnection = dummyURL.openConnection(); openConnection.setDefaultUseCaches(true); } catch (MalformedURLException e1) { // TODO Auto-generated catch block logger.log(Level.WARNING, "Malformed URL", e1); } catch (IOException e) { // TODO Auto-generated catch block logger.log(Level.WARNING, "IO problem", e); } // Setup the services this has to be done before addDatabase // because addDatabase initializes the OTPackages loaded by that // database // Add ourself as a service, this is needed so serviceContext.addService(OTrunk.class, this); if(services != null) { for(int i=0; i<services.size(); i++){ // there might be a better way to make this type safe. // it seems like the compiler should be able to figure out it is safe // but instead the suppress warnings is added to remove the warning here OTrunkServiceEntry entry = services.get(i); serviceContext.addService(entry.serviceInterface, entry.service); } } serviceContext.addService(OTControllerRegistry.class, new OTControllerRegistryImpl()); // We should look up if there are any sevices. try { if(systemDb != null){ this.systemDb = systemDb; systemObjectService = initObjectService(systemDb, "system"); } this.rootDb = db; rootObjectService = initObjectService(rootDb, "root"); if(systemObjectService == null){ // there is no real system db so just use the main db this.systemDb = db; systemObjectService = rootObjectService; } OTSystem otSystem = getSystem(); if(otSystem == null){ logger.warning("No OTSystem object found"); return; } // This is deprecated but we use it anyhow for backward compatibility OTObjectList serviceList = otSystem.getServices(); OTObjectList bundleList = otSystem.getBundles(); if(serviceList.size() > 0 && bundleList.size() > 0){ logger.warning("Both OTSystem.services and OTSystem.bundles are being used. OTSystem.services is deprecated"); } ArrayList<OTObject> combined = new ArrayList<OTObject>(); combined.addAll(serviceList.getVector()); combined.addAll(bundleList.getVector()); for(int i=0; i<combined.size(); i++){ OTBundle bundle = (OTBundle)combined.get(i); if(bundle != null){ bundle.registerServices(serviceContext); } } for(int i=0; i<combined.size(); i++){ OTBundle bundle = (OTBundle)combined.get(i); if(bundle != null){ bundle.initializeBundle(serviceContext); } } } catch (Exception e) { logger.log(Level.WARNING, "Error registering and initializing bundles and services", e); } } /** * * @see org.concord.framework.otrunk.OTrunk#getOTID(java.lang.String) */ public OTID getOTID(String otidStr) { return OTIDFactory.createOTID(otidStr); } /* (non-Javadoc) */ public <T extends OTObject> T createObject(Class<T> objectClass) throws Exception { return rootObjectService.createObject(objectClass); } public void setRoot(OTObject obj) throws Exception { // FIXME this doesn't do a good job if there // is an OTSystem OTID id = obj.getGlobalId(); rootDb.setRoot(id); } public OTSystem getSystem() throws Exception { OTDataObject systemDO = systemDb.getRoot(); if (systemDO == null) { return null; } OTID systemID = systemDO.getGlobalId(); OTObject systemObject = systemObjectService.getOTObject(systemID); if(systemObject instanceof OTSystem){ return (OTSystem) systemObject; } return null; } public OTObject getRealRoot() throws Exception { OTDataObject rootDO = getRootDataObject(); if(rootDO == null) { return null; } return rootObjectService.getOTObject(rootDO.getGlobalId()); } public OTObject getRoot() throws Exception { OTObject root = getRealRoot(); if(root instanceof OTSystem) { return ((OTSystem)root).getRoot(); } return root; } /** * return the database that is serving this id * currently there is only one database, so this is * easy * * @param id * @return */ public OTDatabase getOTDatabase(OTID id) { // look for the database that contains this id if(id == null) { return null; } synchronized(databases) { for (OTDatabase db : databases) { if(db.contains(id)) { return db; } } } return null; } public void close() { rootDb.close(); } /** * Warning: this is method should only be used when you don't know * which object is requesting the new OTObject. The requestion object * is currently used to keep the context of user mode or authoring mode * @param childID * @return * @throws Exception */ public OTObject getOTObject(OTID childID) throws Exception { return rootObjectService.getOTObject(childID); } public <T> T getService(Class<T> serviceInterface) { return serviceContext.getService(serviceInterface); } public OTObjectServiceImpl createObjectService(OTDatabase db) { OTObjectServiceImpl objService = new OTObjectServiceImpl(this); objService.setCreationDb(db); objService.setMainDb(db); registerObjectService(objService, "" + db); return objService; } public OTObjectServiceImpl createObjectService(OTOverlay overlay) { // create an object service for the overlay OverlayImpl myOverlay = new OverlayImpl(overlay); CompositeDatabase db = new CompositeDatabase(this.getDataObjectFinder(), myOverlay); OTObjectServiceImpl objService = this.createObjectService(db); return objService; } public void registerObjectService(OTObjectServiceImpl objService, String label) { objectServices.add(objService); if(OTConfig.isTrace()){ objService.addObjectServiceListener(new TraceListener(label)); } } public void removeObjectService(OTObjectServiceImpl objService) { objectServices.remove(objService); } public void registerUserSession(OTUserSession userSession) throws Exception { userSession.setOTrunk(this); userSession.load(); } /** * the parentObjectService needs to be passed in so the returned object * uses the correct layers based on the context in which this method is * called. * * @param url * @param parentObjectService * @return * @throws Exception */ public OTObject getExternalObject(URL url, OTObjectService parentObjectService) throws Exception { return getExternalObject(url, parentObjectService, false); } /** * the parentObjectService needs to be passed in so the returned object * uses the correct layers based on the context in which this method is * called. * * @param url * @param parentObjectService * @param reload * @return * @throws Exception */ public OTObject getExternalObject(URL url, OTObjectService parentObjectService, boolean reload) throws Exception { OTObjectServiceImpl externalObjectService = loadDatabase(url, reload); // get the root object either the real root or the system root OTObject root = getRoot(externalObjectService); return parentObjectService.getOTObject(root.getGlobalId(), reload); } protected OTObjectServiceImpl getExistingObjectService(XMLDatabase includeDb) { // we've already loaded this database. // It is nice to print an error here because the equals method of the databases // just use the database id. So if 2 databases have the same id then it will // seem like the database has already been loaded. // FIXME this should check the url of the database, and only print this message // if the urls are different. for (OTObjectServiceImpl objectService : objectServices) { OTDatabase db = objectService.getMainDb(); if(db.equals(includeDb)){ return objectService; } } logger.warning("Cannot find objectService for database: " + includeDb.getDatabaseId()); return null; } protected OTObjectServiceImpl loadDatabase(URL url) throws Exception { return loadDatabase(url, false); } protected OTObjectServiceImpl loadDatabase(URL url, boolean reload) throws Exception { // first see if we have a database with the same context url synchronized(databases) { XMLDatabase dbToRemove = null; for (OTDatabase db : databases) { if(!(db instanceof XMLDatabase)) continue; XMLDatabase xmlDatabase = (XMLDatabase) db; URL contextURL = xmlDatabase.getContextURL(); if (contextURL == null) { logger.info("Database without a context url! " + xmlDatabase.getDatabaseId()); } else if (contextURL.equals(url)){ if (reload) { // remove the current database, so we can reload it below logger.info("Removing database so we can reload it."); dbToRemove = xmlDatabase; break; } else { return getExistingObjectService(xmlDatabase); } } } if (dbToRemove != null) { databases.remove(dbToRemove); removeObjectService(getExistingObjectService(dbToRemove)); } } XMLDatabase includeDb = new XMLDatabase(url); // load the data base if(databases.contains(includeDb)){ logger.info("already loaded database with id: " + includeDb.getDatabaseId() + " database: " + includeDb.getContextURL().toExternalForm() + " will not be loaded again"); return getExistingObjectService(includeDb); } // register it with the OTrunkImpl // well track the resource info to be safe here. includeDb.setTrackResourceInfo(true); includeDb.loadObjects(); return initObjectService(includeDb, url.toExternalForm()); } protected OTObject getRoot(OTObjectServiceImpl objectService) throws Exception { OTDatabase db = objectService.getMainDb(); OTDataObject rootDO = db.getRoot(); OTObject root = objectService.getOTObject(rootDO.getGlobalId()); if(root instanceof OTSystem) { return ((OTSystem)root).getRoot(); } return root; } public OTObjectServiceImpl initObjectService(OTDatabase db, String logLabel) throws Exception { return initObjectService(db, logLabel, true); } public OTObjectServiceImpl initObjectService(OTDatabase db, String logLabel, boolean loadIncludes) throws Exception { addDatabase(db); OTObjectServiceImpl objectService = createObjectService(db); if(OTConfig.isTrace()) { objectService.addObjectServiceListener(new TraceListener(logLabel + ": " + db)); } if(loadIncludes){ loadIncludes(objectService); } return objectService; } protected void loadIncludes(OTObjectServiceImpl objectService) throws Exception { OTDatabase db = objectService.getMainDb(); OTDataObject rootDO = db.getRoot(); if (rootDO == null) { return; } OTID rootID = rootDO.getGlobalId(); OTObject otRoot = objectService.getOTObject(rootID); if(!(otRoot instanceof OTSystem)){ return; } OTSystem otSystem = (OTSystem) otRoot; OTObjectList includes = otSystem.getIncludes(); for(int i=0; i<includes.size(); i++){ OTInclude include = (OTInclude) includes.get(i); URL hrefUrl = include.getHref(); try { loadDatabase(hrefUrl); } catch (Exception e) { logger.log(Level.WARNING, "Error while loading database. Trying to continue.", e); continue; } } } public void reloadOverlays(OTUserSession userSession) throws Exception { OTUserObject userObject = userSession.getUserObject(); OTID userId = userObject.getUserId(); OTReferenceMap refMap = userSession.getReferenceMap(); // need to make a new composite database. // the user database should remain the same. OTDatabase oldCompositeDB = compositeDatabases.remove(userId); userObjectServices.remove(userId); databases.remove(oldCompositeDB); registerReferenceMap(refMap); } public OTObjectService registerReferenceMap(OTReferenceMap userStateMap) throws Exception { OTObjectServiceImpl userObjService; OTUser user = userStateMap.getUser(); if(!users.contains(user)){ users.add(user); } OTID userId = user.getUserId(); CompositeDatabase userDb = new CompositeDatabase(dataObjectFinder, userStateMap); addDatabase(userDb); userObjService = createObjectService(userDb); compositeDatabases.put(userId, userDb); userObjectServices.put(userId, userObjService); // After the user database is completely setup, now we get the overlays. // This way the user can change the overlays list and those changes will // affect the overlay list when they are loaded. // The overlay list is added to one overlay at a time. This allows // overlays to also modify the list or its values. // OTOverlayGroup objects are useful so an overlay or user can insert // an overlay into the list without modifying the whole overlay list. try { ArrayList<Overlay> overlays = null; OTObjectList otOverlays = getSystemOverlays(user); if(otOverlays != null && otOverlays.size() > 0){ overlays = new ArrayList<Overlay>(); userDb.setOverlays(overlays); for(int i=0; i<otOverlays.size(); i++){ OTOverlay otOverlay; OTObject otOverlayObj = otOverlays.get(i); if (otOverlayObj instanceof OTIncludeRootObject) { otOverlayObj = ((OTIncludeRootObject)otOverlayObj).getReference(); } if(otOverlayObj instanceof OTOverlayGroup){ OTObjectList members = ((OTOverlayGroup)otOverlayObj).getOverlays(); for(int j=0; j<members.size(); j++){ otOverlay = (OTOverlay) members.get(j); Overlay overlay = new OverlayImpl(otOverlay); overlays.add(overlay); } } else { otOverlay = (OTOverlay) otOverlayObj; Overlay overlay = new OverlayImpl(otOverlay); overlays.add(overlay); } } } } catch (Exception e) { // TODO Auto-generated catch block logger.log(Level.WARNING, "Error register overlays", e); } return userObjService; } public Hashtable<OTID, CompositeDatabase> getCompositeDatabases() { return compositeDatabases; } public Vector<OTUser> getUsers() { return users; } public boolean hasUserModified(OTObject authoredObject, OTUser user) throws Exception { OTObject userObject = getUserRuntimeObject(authoredObject, user); return isModifiedInTopOverlay(userObject); } /** * Check to see if the object is modified in the top overlay. Note that this DOES NOT return true if only a child object is modified. * @param otObject * @return */ public boolean isModifiedInTopOverlay(OTObject otObject) { OTDataObject dataObject = OTObjectServiceImpl.getOTDataObject(otObject); if(dataObject instanceof CompositeDataObject) { OTDataObject overlayModifications = ((CompositeDataObject)dataObject).getActiveDeltaObject(); return overlayModifications != null; } else { logger.finest("This object isn't from an Overlay"); } return false; } public boolean isComposite(OTObject otObject) { OTDataObject dataObject = OTObjectServiceImpl.getOTDataObject(otObject); if(dataObject instanceof CompositeDataObject) { return ((CompositeDataObject)dataObject).isComposite(); } else { return false; } } public boolean isModified(OTObject otObject, OTObjectService objService, boolean includingChildren) { boolean isBaseModified = isModifiedInTopOverlay(otObject); if (isBaseModified) { return true; } if (includingChildren) { OTID objId = otObject.getGlobalId().getMappedId(); ArrayList<ArrayList<OTDataPropertyReference>> references = getOutgoingReferences(objId, true); for (ArrayList<OTDataPropertyReference> path : references) { OTID id = path.get(path.size()-1).getDest(); try { OTObject obj = objService.getOTObject(id); if (obj == null) { continue; } if (isModifiedInTopOverlay(obj)) { return true; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return false; } public void addDatabase(OTDatabase db) { if(!databases.contains(db)) { databases.add(db); ArrayList<Class<? extends OTPackage>> packageClasses = db.getPackageClasses(); if(packageClasses != null){ for(int i=0; i<packageClasses.size(); i++){ registerPackageClass(packageClasses.get(i)); } } } } /** * @param class1 */ public void registerPackageClass(Class<? extends OTPackage> packageClass) { // check to see if this package has already been registered if(registeredPackageClasses.contains(packageClass)){ return; } registeredPackageClasses.add(packageClass); OTPackage otPackage; try { otPackage = packageClass.newInstance(); Class<? extends OTPackage> [] dependencies = otPackage.getPackageDependencies(); if(dependencies != null){ for (Class<? extends OTPackage> dependency : dependencies) { registerPackageClass(dependency); } } otPackage.initialize(this); } catch (InstantiationException e) { // TODO Auto-generated catch block logger.log(Level.WARNING, "Error registering package dependencies", e); } catch (IllegalAccessException e) { // TODO Auto-generated catch block logger.log(Level.WARNING, "Error registering package dependencies", e); } return; } public OTObject getUserRuntimeObject(OTObject authoredObject, OTUser user) throws Exception { authoredObject = getRuntimeAuthoredObject(authoredObject); OTID authoredId = authoredObject.getGlobalId(); OTID userId = user.getUserId(); OTObjectService objService = userObjectServices.get(userId); // the objService should be non null if not this is coding error // that needs to be fixed so we will just let it throw an null pointer // exception return objService.getOTObject(authoredId); } /** * This method is a legacy method. It should be generalized now that "user objects" and * "author objects" are just specific versions of "overlay delta objects" and "base objects" * * @param userObject * @param user * @return * @throws Exception */ public OTObject getRuntimeAuthoredObject(OTObject userObject, OTUser user) throws Exception { OTID objectId = userObject.getGlobalId(); OTID userId = user.getUserId(); if(!(objectId instanceof OTTransientMapID)){ return userObject; } CompositeDatabase db = compositeDatabases.get(userId); //logger.finer("is relative"); Object objectMapToken = ((OTTransientMapID) objectId).getMapToken(); if(objectMapToken != null && objectMapToken == db.getDatabaseId()) { //logger.finer(" equals to databaseid"); objectId = ((OTTransientMapID) objectId).getMappedId(); //logger.finer(": " + objectId.toString()); return getOTObject(objectId); } return userObject; } /** * Get the base "authored" object for a particular object, using the root object service to resolve the object * @param otObject * @return * @throws Exception */ public <T extends OTObject> T getRuntimeAuthoredObject(T otObject) throws Exception { return getRuntimeAuthoredObject(otObject, getRootObjectService()); } /** * Get the base "authored" object for a particular object, using a particular object service to resolve the object * @param otObject * @param objService * @return * @throws Exception */ public <T extends OTObject> T getRuntimeAuthoredObject(T otObject, OTObjectService objService) throws Exception { OTID id = otObject.getGlobalId(); if (id instanceof OTTransientMapID) { OTID realID = ((OTTransientMapID) id).getMappedId(); return (T) objService.getOTObject(realID); } return otObject; } public OTObject getRootObject(OTDatabase db) throws Exception { OTDataObject rootDO = db.getRoot(); OTObject rootObject = rootObjectService.getOTObject(rootDO.getGlobalId()); return rootObject; } /** * @return */ public OTDataObject getRootDataObject() throws Exception { return rootDb.getRoot(); } public OTObjectList getSystemOverlays(OTUser user) throws Exception { OTObject root = getRealRoot(); if(!(root instanceof OTSystem)) { return null; } OTSystem userRoot = (OTSystem) getUserRuntimeObject(root, user); return userRoot.getOverlays(); } /** * @return */ public OTObject getFirstObjectNoUserData() throws Exception { OTObject root = getRealRoot(); if(!(root instanceof OTSystem)) { return null; } return ((OTSystem)root).getFirstObjectNoUserData(); } void putLoadedObject(OTObject otObject, OTID otId) { WeakReference<OTObject> objRef = new WeakReference<OTObject>(otObject); loadedObjects.put(otId, objRef); } OTObject getLoadedObject(OTID otId, boolean reload) { if (reload) { loadedObjects.remove(otId); return null; } OTObject otObject = null; Reference<OTObject> otObjectRef = loadedObjects.get(otId); if(otObjectRef != null) { otObject = otObjectRef.get(); } if(otObject != null) { return otObject; } loadedObjects.remove(otId); return null; } /** * This method is used by object services that can't handle a requested object * this happens in reports when a report object needs to access a user object. * * It might be possible to clean this up by explicitly giving the object service * of the report access to the users objects. * * @param childID * @return * @throws Exception */ OTObject getOrphanOTObject(OTID childID, OTObjectServiceImpl oldService) throws Exception { return getOrphanOTObject(childID, oldService, false); } OTObject getOrphanOTObject(OTID childID, OTObjectServiceImpl oldService, boolean reload) throws Exception { for(int i=0; i<objectServices.size(); i++) { OTObjectServiceImpl objService = objectServices.get(i); // To avoid infinite loop, the objService must not equal to oldService if(objService.managesObject(childID) && objService != oldService) { return objService.getOTObject(childID, reload); } } logger.warning("Data object is not found for: " + childID); return null; } public OTObjectServiceImpl getRootObjectService() { return rootObjectService; } public static OTClass getOTClass(String className) { return otClassMap.get(className); } public static void putOTClass(String className, OTClass otClass) { otClassMap.put(className, otClass); } public OTDataObjectFinder getDataObjectFinder() { return dataObjectFinder; } public void localSaveData(XMLDatabase xmldb) throws Exception { if (xmldb.getSourceURL() == null) { throw new MalformedURLException("Invalid source URL on XMLDatabase: " + xmldb.getDatabaseId().toString()); } localSaveData(xmldb, xmldb.getSourceURL()); } public void localSaveData(OTDatabase db, File file) throws Exception { if (db instanceof XMLDatabase) { XMLDatabase xmldb = (XMLDatabase) db; if (! xmldb.getSourceURL().equals(file.toURL())) { xmldb.setSourceURL(file.toURL()); } } DataOutputStream urlDataOut = new DataOutputStream(new FileOutputStream(file)); ExporterJDOM.export(urlDataOut, db.getRoot(), db); urlDataOut.flush(); urlDataOut.close(); if (db instanceof XMLDatabase) { ((XMLDatabase)db).setSourceVerified(true); } } public void localSaveData(OTDatabase db, URL url) throws Exception { File f = new File(url.getFile()); localSaveData(db, f); } public void remoteSaveData(XMLDatabase xmldb, String method) throws HTTPRequestException,Exception { remoteSaveData(xmldb, method, null); } public void remoteSaveData(XMLDatabase xmldb, String method, Authenticator auth) throws HTTPRequestException,Exception { if (xmldb.getSourceURL() == null) { throw new MalformedURLException("Invalid source URL on XMLDatabase: " + xmldb.getDatabaseId().toString()); } remoteSaveData(xmldb, xmldb.getSourceURL(), method, auth); } public void remoteSaveData(OTDatabase db, URL remoteURL, String method) throws HTTPRequestException,Exception { remoteSaveData(db, remoteURL, method, null); } public void remoteSaveData(OTDatabase db, URL remoteURL, String method, Authenticator auth) throws HTTPRequestException,Exception { HttpURLConnection urlConn; DataOutputStream urlDataOut; BufferedReader urlDataIn; // If method isn't "POST" or "PUT", throw an exception if (!(method.compareTo(OTViewer.HTTP_POST) == 0 || method.compareTo(OTViewer.HTTP_PUT) == 0)) { throw new Exception("Invalid HTTP Request method for data saving"); } if (db instanceof XMLDatabase) { XMLDatabase xmldb = (XMLDatabase) db; if (xmldb.getSourceURL() != null && ! xmldb.getSourceURL().equals(remoteURL)) { ((XMLDatabase)db).setSourceURL(remoteURL); } } if (auth != null) { Authenticator.setDefault(auth); } urlConn = (HttpURLConnection) remoteURL.openConnection(); urlConn.setDoInput(true); urlConn.setDoOutput(true); urlConn.setUseCaches(false); urlConn.setRequestMethod(method); urlConn.setRequestProperty("Content-Type", "application/xml"); // Send POST output. urlDataOut = new DataOutputStream(urlConn.getOutputStream()); ExporterJDOM.export(urlDataOut, db.getRoot(), db); urlDataOut.flush(); urlDataOut.close(); // Get response data. urlDataIn = new BufferedReader(new InputStreamReader(new DataInputStream( urlConn.getInputStream()))); String str; String response = ""; while (null != ((str = urlDataIn.readLine()))) { response += str + "\n"; } urlDataIn.close(); // Need to trap non-HTTP 200/300 responses and throw an exception (if an // exception isn't thrown already) and capture the exceptions upstream int code = urlConn.getResponseCode(); if (code >= 400) { throw new HTTPRequestException("HTTP Response: " + urlConn.getResponseMessage() + "\n\n" + response, urlConn.getResponseCode()); } urlConn.disconnect(); if (db instanceof XMLDatabase) { ((XMLDatabase)db).setDirty(false); ((XMLDatabase)db).setSourceVerified(true); } } /** * @param sailSavingDisabled true if SAIL is set to not return learner data */ public void setSailSavingDisabled(boolean sailSavingDisabled) { this.sailSavingDisabled = sailSavingDisabled; System.setProperty("sail.data.saving", Boolean.toString(! sailSavingDisabled)); } /** * @return true if SAIL is set to not return learner data */ public boolean isSailSavingDisabled() { return sailSavingDisabled; } public <T extends OTObject> ArrayList<T> getAllObjects(Class<T> klass) { return getAllObjects(klass, getRootObjectService()); } public <T extends OTObject> ArrayList<T> getAllObjects(Class<T> klass, OTObjectService objService) { logger.finest("Getting all objects for class: " + klass.getName()); ArrayList<T> allObjects = new ArrayList<T>(); logger.finest("Datases: " + databases.size()); synchronized(databases) { for (OTDatabase db : databases) { logger.finest("Searching db: " + db.getURI()); HashMap<OTID, ? extends OTDataObject> map = db.getDataObjects(); logger.finest("db has " + map.size() + " objects"); for (Entry<OTID, ? extends OTDataObject> entry : map.entrySet()) { OTDataObject dataObj = entry.getValue(); OTID id = entry.getKey(); logger.finest("Data object class is: " + dataObj.getType().getClassName()); try { Class<?> objClass = Class.forName(dataObj.getType().getClassName()); if (klass.isAssignableFrom(objClass)) { logger.finest("It's a match! Adding it."); allObjects.add((T) objService.getOTObject(id)); } } catch (ClassNotFoundException e) { logger.log(Level.WARNING, "Couldn't instantiate class: " + dataObj.getType().getClassName(), e); } catch (Exception e) { logger.log(Level.WARNING, "Couldn't get OTObject for object: " + id.toExternalForm(), e); } } } } return allObjects; } public ArrayList<ArrayList<OTDataPropertyReference>> getIncomingReferences(OTID objectID) { return getIncomingReferences(objectID, null, false, null); } public ArrayList<ArrayList<OTDataPropertyReference>> getIncomingReferences(OTID objectID, boolean getIndirectReferences) { return getIncomingReferences(objectID, null, getIndirectReferences, null); } public ArrayList<ArrayList<OTDataPropertyReference>> getIncomingReferences(OTID objectID, Class<?> filterClass, boolean getIndirectReferences, ArrayList<OTID> excludeIDs) { ArrayList<OTDataPropertyReference> path = new ArrayList<OTDataPropertyReference>(); return getReferences(true, objectID, filterClass, getIndirectReferences, path, excludeIDs); } private ArrayList<ArrayList<OTDataPropertyReference>> getReferences(boolean incoming, OTID objectID, Class<?> filterClass, boolean getIndirectReferences, ArrayList<OTDataPropertyReference> currentPath, ArrayList<OTID> excludeIDs) { ArrayList<ArrayList<OTDataPropertyReference>> allParents = new ArrayList<ArrayList<OTDataPropertyReference>>(); if (excludeIDs == null) { excludeIDs = new ArrayList<OTID>(); } // XXX Should we be searching all databases? synchronized(databases) { for (OTDatabase db : databases) { try { ArrayList<OTDataPropertyReference> parents = null; if (incoming) { parents = db.getIncomingReferences(objectID); } else { parents = db.getOutgoingReferences(objectID); } if (parents != null) { logger.finest("Found " + parents.size() + " references"); for (OTDataPropertyReference reference : parents) { /* FIXME by skipping objects we've seen already, it's possible that we're not including all of the possible paths to an object. * For instance, if A indirectly references D through both B and C, only one of A -> B -> D or A -> C -> D will be returned. * So while this code *will* find all the correct endpoints, it won't necessarily reflect all of the possible paths between those endpoints. */ OTID pId; if (incoming) { pId= reference.getSource(); } else { pId = reference.getDest(); } if (! excludeIDs.contains(pId)) { logger.finest("Found reference id: " + pId); excludeIDs.add(pId); ArrayList<OTDataPropertyReference> pPath = (ArrayList<OTDataPropertyReference>) currentPath.clone(); pPath.add(reference); OTDataObject parentObj = db.getOTDataObject(null, pId); if (parentObj != null) { if (filterClass != null) { logger.finest("Filter class: " + filterClass.getSimpleName() + ", parent class: " + parentObj.getType().getClassName()); } if (filterClass == null || filterClass.isAssignableFrom(Class.forName(parentObj.getType().getClassName()))) { logger.finest("Found a matching parent: " + parentObj.getGlobalId()); allParents.add(pPath); } if (getIndirectReferences) { logger.finest("recursing"); allParents.addAll(getReferences(incoming, pId, filterClass, true, pPath, excludeIDs)); logger.finest("unrecursing"); } } else { logger.warning("Had parent id but no real object!: " + pId); } } else { logger.finest("Already seen this id: " + pId); } } } else { logger.finest("null parents"); } } catch (Exception e) { // TODO Auto-generated catch block logger.log(Level.WARNING, "Error finding parents", e); } } } return allParents; } public ArrayList<ArrayList<OTDataPropertyReference>> getIncomingReferences(OTID objectID, Class<?> filterClass, boolean getIndirectReferences) { logger.finer("Finding references for: " + objectID + " with class: " + (filterClass == null ? "null" : filterClass.getName()) + " and recursion: " + getIndirectReferences); ArrayList<ArrayList<OTDataPropertyReference>> parents = getIncomingReferences(objectID, filterClass, getIndirectReferences, null); logger.finer("found " + parents.size() + " matching parents"); return parents; } public ArrayList<ArrayList<OTDataPropertyReference>> getOutgoingReferences(OTID objectID) { return getOutgoingReferences(objectID, null, false, null); } public ArrayList<ArrayList<OTDataPropertyReference>> getOutgoingReferences(OTID objectID, boolean getIndirectReferences) { return getOutgoingReferences(objectID, null, getIndirectReferences, null); } public ArrayList<ArrayList<OTDataPropertyReference>> getOutgoingReferences(OTID objectID, Class<?> filterClass, boolean getIndirectReferences, ArrayList<OTID> excludeIDs) { return getReferences(false, objectID, filterClass, getIndirectReferences, new ArrayList<OTDataPropertyReference>(), excludeIDs); } public ArrayList<ArrayList<OTDataPropertyReference>> getOutgoingReferences(OTID objectID, Class<?> filterClass, boolean getIndirectReferences) { logger.finest("Finding references for: " + objectID + " with class: " + (filterClass == null ? "null" : filterClass.getName()) + " and recursion: " + getIndirectReferences); ArrayList<ArrayList<OTDataPropertyReference>> parents = getOutgoingReferences(objectID, filterClass, getIndirectReferences, null); logger.finest("found " + parents.size() + " matching parents"); return parents; } }