0001 /*
0002 * Copyright (c) 1995-2010, The University of Sheffield. See the file
0003 * COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0004 *
0005 * This file is part of GATE (see http://gate.ac.uk/), and is free
0006 * software, licenced under the GNU Library General Public License,
0007 * Version 2, June 1991 (in the distribution as file licence.html,
0008 * and also available at http://gate.ac.uk/gate/licence.html).
0009 *
0010 * Valentin Tablan 25/10/2001
0011 *
0012 * $Id: PersistenceManager.java 13597 2011-04-05 01:22:52Z johann_p $
0013 *
0014 */
0015 package gate.util.persistence;
0016
0017 import com.thoughtworks.xstream.XStream;
0018 import com.thoughtworks.xstream.converters.reflection.FieldDictionary;
0019 import com.thoughtworks.xstream.converters.reflection.Sun14ReflectionProvider;
0020 import com.thoughtworks.xstream.converters.reflection.XStream12FieldKeySorter;
0021 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
0022 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
0023 import com.thoughtworks.xstream.io.xml.*;
0024
0025 import java.io.*;
0026 import java.net.*;
0027 import java.text.NumberFormat;
0028 import java.util.*;
0029
0030 import javax.xml.stream.*;
0031
0032 import org.apache.log4j.Logger;
0033
0034 import gate.*;
0035 import gate.creole.*;
0036 import gate.event.ProgressListener;
0037 import gate.event.StatusListener;
0038 import gate.persist.GateAwareObjectInputStream;
0039 import gate.persist.PersistenceException;
0040 import gate.util.*;
0041
0042 /**
0043 * This class provides utility methods for saving resources through
0044 * serialisation via static methods.
0045 *
0046 * It now supports both native and xml serialization.
0047 */
0048 public class PersistenceManager {
0049
0050 private static final boolean DEBUG = false;
0051
0052 /**
0053 * A reference to an object; it uses the identity hashcode and the
0054 * equals defined by object identity. These values will be used as
0055 * keys in the {link #existingPersistentReplacements} map.
0056 */
0057 static protected class ObjectHolder {
0058 ObjectHolder(Object target) {
0059 this.target = target;
0060 }
0061
0062 public int hashCode() {
0063 return System.identityHashCode(target);
0064 }
0065
0066 public boolean equals(Object obj) {
0067 if(obj instanceof ObjectHolder)
0068 return ((ObjectHolder)obj).target == this.target;
0069 else return false;
0070 }
0071
0072 public Object getTarget() {
0073 return target;
0074 }
0075
0076 private Object target;
0077 }// static class ObjectHolder{
0078
0079 /**
0080 * This class is used as a marker for types that should NOT be
0081 * serialised when saving the state of a gate object. Registering this
0082 * type as the persistent equivalent for a specific class (via
0083 * {@link PersistenceManager#registerPersistentEquivalent(Class , Class)})
0084 * effectively stops all values of the specified type from being
0085 * serialised.
0086 *
0087 * Maps that contain values that should not be serialised will have
0088 * that entry removed. In any other places where such values occur
0089 * they will be replaced by null after deserialisation.
0090 */
0091 public static class SlashDevSlashNull implements Persistence {
0092 /**
0093 * Does nothing
0094 */
0095 public void extractDataFromSource(Object source)
0096 throws PersistenceException {
0097 }
0098
0099 /**
0100 * Returns null
0101 */
0102 public Object createObject() throws PersistenceException,
0103 ResourceInstantiationException {
0104 return null;
0105 }
0106
0107 static final long serialVersionUID = -8665414981783519937L;
0108 }
0109
0110 /**
0111 * URLs get upset when serialised and deserialised so we need to
0112 * convert them to strings for storage. In the case of
0113 * "file:" URLs the relative path to the persistence file
0114 * will actually be stored, except when the URL refers to a resource
0115 * within the current GATE home directory in which case the relative path
0116 * to the GATE home directory will be stored. In that case a warning
0117 * is issued to
0118 */
0119 public static class URLHolder implements Persistence {
0120 /**
0121 * Populates this Persistence with the data that needs to be stored
0122 * from the original source object.
0123 */
0124 public void extractDataFromSource(Object source)
0125 throws PersistenceException {
0126 final Logger logger = Logger.getLogger(URLHolder.class);
0127 try {
0128 URL url = (URL)source;
0129 if(url.getProtocol().equals("file")) {
0130 try {
0131 String pathMarker = relativePathMarker;
0132 File gateCanonicalHome = null;
0133 URL urlPersistenceFilePath = currentPersistenceFile().toURI().toURL();
0134 // try to get the canonical representation of the file both URLs
0135 // so that the relative paths are not getting confused by symbolic
0136 // links. If this fails, just proceed with the original URLs
0137 try {
0138 urlPersistenceFilePath =
0139 currentPersistenceFile().getCanonicalFile().toURI().toURL();
0140 url =
0141 Files.fileFromURL(url).getCanonicalFile().toURI().toURL();
0142 } catch (IOException ex) {
0143 // ignore
0144 }
0145
0146 // if the persistence file does NOT reside in the GATE home
0147 // tree and if the URL references something in the GATE home
0148 // tree, use $gatehome$ instead of $relpath$
0149 if(currentUseGateHome() || currentWarnAboutGateHome()) {
0150 try {
0151 String persistenceFilePathName =
0152 currentPersistenceFile().getCanonicalPath();
0153 String gateHomePathName = Gate.getGateHome().getCanonicalPath();
0154 gateCanonicalHome = Gate.getGateHome().getCanonicalFile();
0155 String urlPathName = Files.fileFromURL(url).getCanonicalPath();
0156 //logger.debug("persistenceFilePathName "+persistenceFilePathName);
0157 //logger.debug("gateHomePathName "+gateHomePathName);
0158 //logger.debug("urlPathName "+urlPathName);
0159 if(!persistenceFilePathName.startsWith(gateHomePathName) &&
0160 urlPathName.startsWith(gateHomePathName)) {
0161 //logger.debug("Setting path marker to "+gatehomePathMarker);
0162 if(currentWarnAboutGateHome()) {
0163 if(!currentHaveWarnedAboutGateHome().getValue()) {
0164 logger.warn(
0165 "\nYour application is using some of the plug-ins "+
0166 "distributed with GATE, and may not work as expected "+
0167 "with different versions of GATE. You should consider "+
0168 "making private local copies of the plug-ins, and "+
0169 "distributing those with your application.");
0170 currentHaveWarnedAboutGateHome().setValue(true);
0171 }
0172 // the actual URL is shown every time
0173 logger.warn("GATE resource referenced: "+url);
0174 }
0175 if(currentUseGateHome()) {
0176 pathMarker = gatehomePathMarker;
0177 }
0178 }
0179 } catch(IOException ex) {
0180 // do nothing and proceed with using the relativePathMarker
0181 }
0182 }
0183 if(pathMarker.equals(relativePathMarker)) {
0184 urlString = pathMarker
0185 + getRelativePath(urlPersistenceFilePath, url);
0186 } else {
0187 urlString = pathMarker
0188 + getRelativePath(gateCanonicalHome.toURI().toURL(), url);
0189 }
0190 }
0191 catch(MalformedURLException mue) {
0192 urlString = ((URL)source).toExternalForm();
0193 }
0194 }
0195 else {
0196 urlString = ((URL)source).toExternalForm();
0197 }
0198 }
0199 catch(ClassCastException cce) {
0200 throw new PersistenceException(cce);
0201 }
0202 }
0203
0204 /**
0205 * Creates a new object from the data contained. This new object is
0206 * supposed to be a copy for the original object used as source for
0207 * data extraction.
0208 */
0209 public Object createObject() throws PersistenceException {
0210 try {
0211 if(urlString.startsWith(relativePathMarker)) {
0212 URL context = currentPersistenceURL();
0213 return new URL(context, urlString.substring(relativePathMarker
0214 .length()));
0215 } else if(urlString.startsWith(gatehomePathMarker)) {
0216 URL gatehome = Gate.getGateHome().toURI().toURL();
0217 return new URL(gatehome, urlString.substring(gatehomePathMarker.length()));
0218 } else if(urlString.startsWith(gatepluginsPathMarker)) {
0219 URL gateplugins = Gate.getPluginsHome().toURI().toURL();
0220 return new URL(gateplugins, urlString.substring(gatepluginsPathMarker.length()));
0221 } else if(urlString.startsWith(syspropMarker)) {
0222 String urlRestString = urlString.substring(syspropMarker.length());
0223 int dollarindex = urlRestString.indexOf("$");
0224 if(dollarindex > 0) {
0225 String syspropname = urlRestString.substring(0,dollarindex);
0226 String propvalue = System.getProperty(syspropname);
0227 if(propvalue == null) {
0228 throw new PersistenceException("Property '"+syspropname+"' is null in "+urlString);
0229 }
0230 URL propuri = (new File(propvalue)).toURI().toURL();
0231 if(dollarindex == urlRestString.length()) {
0232 return propuri;
0233 } else {
0234 return new URL(propuri, urlRestString.substring(dollarindex+1));
0235 }
0236 } else if(dollarindex == 0) {
0237 throw new PersistenceException("No property name after '"+syspropMarker+"' in "+urlString);
0238 } else {
0239 throw new PersistenceException("No ending $ after '"+syspropMarker+"' in "+urlString);
0240 }
0241 } else {
0242 return new URL(urlString);
0243 }
0244 }
0245 catch(MalformedURLException mue) {
0246 throw new PersistenceException(mue);
0247 }
0248 }
0249
0250 String urlString;
0251
0252 /**
0253 * This string will be used to start the serialisation of URL that
0254 * represent relative paths.
0255 */
0256 private static final String relativePathMarker = "$relpath$";
0257 private static final String gatehomePathMarker = "$gatehome$";
0258 private static final String gatepluginsPathMarker = "$gateplugins$";
0259 private static final String syspropMarker = "$sysprop:";
0260
0261 static final long serialVersionUID = 7943459208429026229L;
0262 }
0263
0264 public static class ClassComparator implements Comparator {
0265 /**
0266 * Compares two {@link Class} values in terms of specificity; the
0267 * more specific class is said to be "smaller" than the
0268 * more generic one hence the {@link Object} class is the
0269 * "largest" possible class. When two classes are not
0270 * comparable (i.e. not assignable from each other) in either
0271 * direction a NotComparableException will be thrown. both input
0272 * objects should be Class values otherwise a
0273 * {@link ClassCastException} will be thrown.
0274 *
0275 */
0276 public int compare(Object o1, Object o2) {
0277 Class c1 = (Class)o1;
0278 Class c2 = (Class)o2;
0279
0280 if(c1.equals(c2)) return 0;
0281 if(c1.isAssignableFrom(c2)) return 1;
0282 if(c2.isAssignableFrom(c1)) return -1;
0283 throw new NotComparableException();
0284 }
0285 }
0286
0287 /**
0288 * Thrown by a comparator when the values provided for comparison are
0289 * not comparable.
0290 */
0291 public static class NotComparableException extends RuntimeException {
0292 public NotComparableException(String message) {
0293 super(message);
0294 }
0295
0296 public NotComparableException() {
0297 }
0298 }
0299
0300 /**
0301 * Recursively traverses the provided object and replaces it and all
0302 * its contents with the appropriate persistent equivalent classes.
0303 *
0304 * @param target the object to be analysed and translated into a
0305 * persistent equivalent.
0306 * @return the persistent equivalent value for the provided target
0307 */
0308 public static Serializable getPersistentRepresentation(Object target)
0309 throws PersistenceException {
0310 if(target == null) return null;
0311 // first check we don't have it already
0312 Persistence res = (Persistence)existingPersistentReplacements
0313 .get().getFirst().get(new ObjectHolder(target));
0314 if(res != null) return res;
0315
0316 Class type = target.getClass();
0317 Class newType = getMostSpecificPersistentType(type);
0318 if(newType == null) {
0319 // no special handler
0320 if(target instanceof Serializable)
0321 return (Serializable)target;
0322 else throw new PersistenceException(
0323 "Could not find a serialisable replacement for " + type);
0324 }
0325
0326 // we have a new type; create the new object, populate and return it
0327 try {
0328 res = (Persistence)newType.newInstance();
0329 }
0330 catch(Exception e) {
0331 throw new PersistenceException(e);
0332 }
0333 if(target instanceof NameBearer) {
0334 StatusListener sListener = (StatusListener)Gate.getListeners().get(
0335 "gate.event.StatusListener");
0336 if(sListener != null) {
0337 sListener.statusChanged("Storing " + ((NameBearer)target).getName());
0338 }
0339 }
0340 res.extractDataFromSource(target);
0341 existingPersistentReplacements.get().getFirst().put(new ObjectHolder(target), res);
0342 return res;
0343 }
0344
0345 public static Object getTransientRepresentation(Object target)
0346 throws PersistenceException, ResourceInstantiationException {
0347
0348 if(target == null || target instanceof SlashDevSlashNull) return null;
0349 if(target instanceof Persistence) {
0350 Object resultKey = new ObjectHolder(target);
0351 // check the cached values; maybe we have the result already
0352 Object result = existingTransientValues.get().getFirst().get(resultKey);
0353 if(result != null) return result;
0354
0355 // we didn't find the value: create it
0356 result = ((Persistence)target).createObject();
0357 existingTransientValues.get().getFirst().put(resultKey, result);
0358 return result;
0359 }
0360 else return target;
0361 }
0362
0363 /**
0364 * Finds the most specific persistent replacement type for a given
0365 * class. Look for a type that has a registered persistent equivalent
0366 * starting from the provided class continuing with its superclass and
0367 * implemented interfaces and their superclasses and implemented
0368 * interfaces and so on until a type is found. Classes are considered
0369 * to be more specific than interfaces and in situations of ambiguity
0370 * the most specific types are considered to be the ones that don't
0371 * belong to either java or GATE followed by the ones that belong to
0372 * GATE and followed by the ones that belong to java.
0373 *
0374 * E.g. if there are registered persistent types for
0375 * {@link gate.Resource} and for {@link gate.LanguageResource} than
0376 * such a request for a {@link gate.Document} will yield the
0377 * registered type for {@link gate.LanguageResource}.
0378 */
0379 protected static Class getMostSpecificPersistentType(Class type) {
0380 // this list will contain all the types we need to expand to
0381 // superclass +
0382 // implemented interfaces. We start with the provided type and work
0383 // our way
0384 // up the ISA hierarchy
0385 List expansionSet = new ArrayList();
0386 expansionSet.add(type);
0387
0388 // algorithm:
0389 // 1) check the current expansion set
0390 // 2) expand the expansion set
0391
0392 // at each expansion stage we'll have a class and three lists of
0393 // interfaces:
0394 // the user defined ones; the GATE ones and the java ones.
0395 List userInterfaces = new ArrayList();
0396 List gateInterfaces = new ArrayList();
0397 List javaInterfaces = new ArrayList();
0398 while(!expansionSet.isEmpty()) {
0399 // 1) check the current set
0400 Iterator typesIter = expansionSet.iterator();
0401 while(typesIter.hasNext()) {
0402 Class result = (Class)persistentReplacementTypes.get(typesIter.next());
0403 if(result != null) {
0404 return result;
0405 }
0406 }
0407 // 2) expand the current expansion set;
0408 // the expanded expansion set will need to be ordered according to
0409 // the
0410 // rules (class >> interface; user interf >> gate interf >> java
0411 // interf)
0412
0413 // at each point we only have at most one class
0414 if(type != null) type = type.getSuperclass();
0415
0416 userInterfaces.clear();
0417 gateInterfaces.clear();
0418 javaInterfaces.clear();
0419
0420 typesIter = expansionSet.iterator();
0421 while(typesIter.hasNext()) {
0422 Class aType = (Class)typesIter.next();
0423 Class[] interfaces = aType.getInterfaces();
0424 // distribute them according to their type
0425 for(int i = 0; i < interfaces.length; i++) {
0426 Class anIterf = interfaces[i];
0427 String interfType = anIterf.getName();
0428 if(interfType.startsWith("java")) {
0429 javaInterfaces.add(anIterf);
0430 }
0431 else if(interfType.startsWith("gate")) {
0432 gateInterfaces.add(anIterf);
0433 }
0434 else userInterfaces.add(anIterf);
0435 }
0436 }
0437
0438 expansionSet.clear();
0439 if(type != null) expansionSet.add(type);
0440 expansionSet.addAll(userInterfaces);
0441 expansionSet.addAll(gateInterfaces);
0442 expansionSet.addAll(javaInterfaces);
0443 }
0444 // we got out the while loop without finding anything; return null;
0445 return null;
0446 }
0447
0448 /**
0449 * Calculates the relative path for a file: URL starting from a given
0450 * context which is also a file: URL.
0451 *
0452 * @param context the URL to be used as context.
0453 * @param target the URL for which the relative path is computed.
0454 * @return a String value representing the relative path. Constructing
0455 * a URL from the context URL and the relative path should
0456 * result in the target URL.
0457 */
0458 public static String getRelativePath(URL context, URL target) {
0459 if(context.getProtocol().equals("file")
0460 && target.getProtocol().equals("file")) {
0461 File contextFile = Files.fileFromURL(context);
0462 File targetFile = Files.fileFromURL(target);
0463
0464 // if the original context URL ends with a slash (i.e. denotes
0465 // a directory), then we pretend we're taking a path relative to
0466 // some file in that directory. This is because the relative
0467 // path from context file:/home/foo/bar to file:/home/foo/bar/baz
0468 // is bar/baz, whereas the path from file:/home/foo/bar/ - with
0469 // the trailing slash - is just baz.
0470 if(context.toExternalForm().endsWith("/")) {
0471 contextFile = new File(contextFile, "__dummy__");
0472 }
0473
0474 List targetPathComponents = new ArrayList();
0475 File aFile = targetFile.getParentFile();
0476 while(aFile != null) {
0477 targetPathComponents.add(0, aFile);
0478 aFile = aFile.getParentFile();
0479 }
0480 List contextPathComponents = new ArrayList();
0481 aFile = contextFile.getParentFile();
0482 while(aFile != null) {
0483 contextPathComponents.add(0, aFile);
0484 aFile = aFile.getParentFile();
0485 }
0486 // the two lists can have 0..n common elements (0 when the files
0487 // are
0488 // on separate roots
0489 int commonPathElements = 0;
0490 while(commonPathElements < targetPathComponents.size()
0491 && commonPathElements < contextPathComponents.size()
0492 && targetPathComponents.get(commonPathElements).equals(
0493 contextPathComponents.get(commonPathElements)))
0494 commonPathElements++;
0495 // construct the string for the relative URL
0496 String relativePath = "";
0497 for(int i = commonPathElements; i < contextPathComponents.size(); i++) {
0498 if(relativePath.length() == 0)
0499 relativePath += "..";
0500 else relativePath += "/..";
0501 }
0502 for(int i = commonPathElements; i < targetPathComponents.size(); i++) {
0503 String aDirName = ((File)targetPathComponents.get(i)).getName();
0504 if(aDirName.length() == 0) {
0505 aDirName = ((File)targetPathComponents.get(i)).getAbsolutePath();
0506 if(aDirName.endsWith(File.separator)) {
0507 aDirName = aDirName.substring(0, aDirName.length()
0508 - File.separator.length());
0509 }
0510 }
0511 // Out.prln("Adding \"" + aDirName + "\" name for " +
0512 // targetPathComponents.get(i));
0513 if(relativePath.length() == 0) {
0514 relativePath += aDirName;
0515 }
0516 else {
0517 relativePath += "/" + aDirName;
0518 }
0519 }
0520 // we have the directory; add the file name
0521 if(relativePath.length() == 0) {
0522 relativePath += targetFile.getName();
0523 }
0524 else {
0525 relativePath += "/" + targetFile.getName();
0526 }
0527
0528 if(target.toExternalForm().endsWith("/")) {
0529 // original target ended with a slash, so relative path should do too
0530 relativePath += "/";
0531 }
0532 try {
0533 URI relativeURI = new URI(null, null, relativePath, null, null);
0534 return relativeURI.getRawPath();
0535 }
0536 catch(URISyntaxException use) {
0537 throw new GateRuntimeException("Failed to generate relative path " +
0538 "between context: " + context + " and target: " + target, use);
0539 }
0540 }
0541 else {
0542 throw new GateRuntimeException("Both the target and the context URLs "
0543 + "need to be \"file:\" URLs!");
0544 }
0545 }
0546
0547 public static void saveObjectToFile(Object obj, File file)
0548 throws PersistenceException, IOException {
0549 saveObjectToFile(obj, file, false, false);
0550 }
0551
0552 public static void saveObjectToFile(Object obj, File file,
0553 boolean usegatehome, boolean warnaboutgatehome)
0554 throws PersistenceException, IOException {
0555 ProgressListener pListener = (ProgressListener)Gate.getListeners()
0556 .get("gate.event.ProgressListener");
0557 StatusListener sListener = (gate.event.StatusListener)Gate
0558 .getListeners().get("gate.event.StatusListener");
0559 long startTime = System.currentTimeMillis();
0560 if(pListener != null) pListener.progressChanged(0);
0561 // The object output stream is used for native serialization,
0562 // but the xstream and filewriter are used for XML serialization.
0563 ObjectOutputStream oos = null;
0564 com.thoughtworks.xstream.XStream xstream = null;
0565 HierarchicalStreamWriter writer = null;
0566 warnAboutGateHome.get().addFirst(warnaboutgatehome);
0567 useGateHome.get().addFirst(usegatehome);
0568 startPersistingTo(file);
0569 try {
0570 if(Gate.getUseXMLSerialization()) {
0571 // Just create the xstream and the filewriter that will later be
0572 // used to serialize objects.
0573 xstream = new XStream(
0574 new Sun14ReflectionProvider(new FieldDictionary(new XStream12FieldKeySorter())),
0575 new StaxDriver(new XStream11XmlFriendlyReplacer())) {
0576 protected boolean useXStream11XmlFriendlyMapper() {
0577 return true;
0578 }
0579 };
0580 FileWriter fileWriter = new FileWriter(file);
0581 writer = new PrettyPrintWriter(fileWriter,
0582 new XmlFriendlyReplacer("-", "_"));
0583 }
0584 else {
0585 oos = new ObjectOutputStream(new FileOutputStream(file));
0586 }
0587
0588 // always write the list of creole URLs first
0589 List urlList = new ArrayList(Gate.getCreoleRegister().getDirectories());
0590 Object persistentList = getPersistentRepresentation(urlList);
0591
0592 Object persistentObject = getPersistentRepresentation(obj);
0593
0594 if(Gate.getUseXMLSerialization()) {
0595 // We need to put the urls and the application itself together
0596 // as xstreams can only hold one object.
0597 GateApplication gateApplication = new GateApplication();
0598 gateApplication.urlList = persistentList;
0599 gateApplication.application = persistentObject;
0600
0601 // Then do the actual serialization.
0602 xstream.marshal(gateApplication, writer);
0603 }
0604 else {
0605 // This is for native serialization.
0606 oos.writeObject(persistentList);
0607
0608 // now write the object
0609 oos.writeObject(persistentObject);
0610 }
0611
0612 }
0613 finally {
0614 finishedPersisting();
0615 if(oos != null) {
0616 oos.flush();
0617 oos.close();
0618 }
0619 if(writer != null) {
0620 // Just make sure that all the xml is written, and the file
0621 // closed.
0622 writer.flush();
0623 writer.close();
0624 }
0625 long endTime = System.currentTimeMillis();
0626 if(sListener != null)
0627 sListener.statusChanged("Storing completed in "
0628 + NumberFormat.getInstance().format(
0629 (double)(endTime - startTime) / 1000) + " seconds");
0630 if(pListener != null) pListener.processFinished();
0631 }
0632 }
0633
0634 /**
0635 * Set up the thread-local state for a new persistence run.
0636 */
0637 private static void startPersistingTo(File file) {
0638 haveWarnedAboutGateHome.get().addFirst(new BooleanFlag(false));
0639 persistenceFile.get().addFirst(file);
0640 existingPersistentReplacements.get().addFirst(new HashMap());
0641 }
0642
0643 /**
0644 * Get the file currently being saved by this thread.
0645 */
0646 private static File currentPersistenceFile() {
0647 return persistenceFile.get().getFirst();
0648 }
0649
0650 private static Boolean currentWarnAboutGateHome() {
0651 return warnAboutGateHome.get().getFirst();
0652 }
0653
0654 private static Boolean currentUseGateHome() {
0655 return useGateHome.get().getFirst();
0656 }
0657
0658 private static BooleanFlag currentHaveWarnedAboutGateHome() {
0659 return haveWarnedAboutGateHome.get().getFirst();
0660 }
0661
0662 /**
0663 * Clean up the thread-local state for the current persistence run.
0664 */
0665 private static void finishedPersisting() {
0666 persistenceFile.get().removeFirst();
0667 if(persistenceFile.get().isEmpty()) {
0668 persistenceFile.remove();
0669 }
0670 existingPersistentReplacements.get().removeFirst();
0671 if(existingPersistentReplacements.get().isEmpty()) {
0672 existingPersistentReplacements.remove();
0673 }
0674 }
0675
0676 public static Object loadObjectFromFile(File file)
0677 throws PersistenceException, IOException,
0678 ResourceInstantiationException {
0679 return loadObjectFromUrl(file.toURI().toURL());
0680 }
0681
0682 public static Object loadObjectFromUrl(URL url) throws PersistenceException,
0683 IOException, ResourceInstantiationException {
0684 ProgressListener pListener = (ProgressListener)Gate.getListeners()
0685 .get("gate.event.ProgressListener");
0686 StatusListener sListener = (gate.event.StatusListener)Gate
0687 .getListeners().get("gate.event.StatusListener");
0688 if(pListener != null) pListener.progressChanged(0);
0689
0690 startLoadingFrom(url);
0691 //the actual stream obtained from the URL. We keep a reference to this
0692 //so we can ensure it gets closed.
0693 InputStream rawStream = null;
0694 try {
0695 long startTime = System.currentTimeMillis();
0696 // Determine whether the file contains an application serialized in
0697 // xml
0698 // format. Otherwise we will assume that it contains native
0699 // serializations.
0700 boolean xmlStream = isXmlApplicationFile(url);
0701 ObjectInputStream ois = null;
0702 HierarchicalStreamReader reader = null;
0703 XStream xstream = null;
0704 // Make the appropriate kind of streams that will be used, depending
0705 // on
0706 // whether serialization is native or xml.
0707 if(xmlStream) {
0708 // we don't want to strip the BOM on XML.
0709 Reader inputReader = new InputStreamReader(
0710 rawStream = url.openStream());
0711 try {
0712 XMLInputFactory inputFactory = XMLInputFactory.newInstance();
0713 XMLStreamReader xsr = inputFactory.createXMLStreamReader(
0714 url.toExternalForm(), inputReader);
0715 reader = new StaxReader(new QNameMap(), xsr);
0716 }
0717 catch(XMLStreamException xse) {
0718 // make sure the stream is closed, on error
0719 inputReader.close();
0720 throw new PersistenceException("Error creating reader", xse);
0721 }
0722
0723 xstream = new XStream(new StaxDriver(new XStream11XmlFriendlyReplacer())) {
0724 protected boolean useXStream11XmlFriendlyMapper() {
0725 return true;
0726 }
0727 };
0728 // make XStream load classes through the GATE ClassLoader
0729 xstream.setClassLoader(Gate.getClassLoader());
0730 // make the XML stream appear as a normal ObjectInputStream
0731 ois = xstream.createObjectInputStream(reader);
0732 }
0733 else {
0734 // use GateAwareObjectInputStream to load classes through the
0735 // GATE ClassLoader if they can't be loaded through the one
0736 // ObjectInputStream would normally use
0737 ois = new GateAwareObjectInputStream(url.openStream());
0738
0739 }
0740 Object res = null;
0741 try {
0742 // first read the list of creole URLs.
0743 Iterator urlIter =
0744 ((Collection)getTransientRepresentation(ois.readObject()))
0745 .iterator();
0746
0747 // and re-register them
0748 while(urlIter.hasNext()) {
0749 URL anUrl = (URL)urlIter.next();
0750 try {
0751 Gate.getCreoleRegister().registerDirectories(anUrl);
0752 }
0753 catch(GateException ge) {
0754 Err.prln("Could not reload creole directory "
0755 + anUrl.toExternalForm());
0756 }
0757 }
0758
0759 // now we can read the saved object in the presence of all
0760 // the right plugins
0761 res = ois.readObject();
0762
0763 // ensure a fresh start
0764 clearCurrentTransients();
0765 res = getTransientRepresentation(res);
0766 long endTime = System.currentTimeMillis();
0767 if(sListener != null)
0768 sListener.statusChanged("Loading completed in "
0769 + NumberFormat.getInstance().format(
0770 (double)(endTime - startTime) / 1000) + " seconds");
0771 return res;
0772 }
0773 catch(ResourceInstantiationException rie) {
0774 if(sListener != null) sListener.statusChanged(
0775 "Failure during instantiation of resources.");
0776 throw rie;
0777 }
0778 catch(PersistenceException pe) {
0779 if(sListener != null) sListener.statusChanged(
0780 "Failure during persistence operations.");
0781 throw pe;
0782 }
0783 catch(Exception ex) {
0784 if(sListener != null) sListener.statusChanged("Loading failed!");
0785 throw new PersistenceException(ex);
0786 } finally {
0787 //make sure the stream gets closed
0788 if (ois != null) ois.close();
0789 if(reader != null) reader.close();
0790 }
0791 }
0792 finally {
0793 if(rawStream != null) rawStream.close();
0794 finishedLoading();
0795 if(pListener != null) pListener.processFinished();
0796 }
0797 }
0798
0799 /**
0800 * Set up the thread-local state for the current loading run.
0801 */
0802 private static void startLoadingFrom(URL url) {
0803 persistenceURL.get().addFirst(url);
0804 existingTransientValues.get().addFirst(new HashMap());
0805 }
0806
0807 /**
0808 * Clear the current list of transient replacements without
0809 * popping them off the stack.
0810 */
0811 private static void clearCurrentTransients() {
0812 existingTransientValues.get().getFirst().clear();
0813 }
0814
0815 /**
0816 * Get the URL currently being loaded by this thread.
0817 */
0818 private static URL currentPersistenceURL() {
0819 return persistenceURL.get().getFirst();
0820 }
0821
0822 /**
0823 * Clean up the thread-local state at the end of a loading run.
0824 */
0825 private static void finishedLoading() {
0826 persistenceURL.get().removeFirst();
0827 if(persistenceURL.get().isEmpty()) {
0828 persistenceURL.remove();
0829 }
0830 existingTransientValues.get().removeFirst();
0831 if(existingTransientValues.get().isEmpty()) {
0832 existingTransientValues.remove();
0833 }
0834 }
0835
0836 /**
0837 * Determine whether the URL contains a GATE application serialized
0838 * using XML.
0839 *
0840 * @param url The URL to check.
0841 * @return true if the URL refers to an xml serialized application,
0842 * false otherwise.
0843 */
0844 private static boolean isXmlApplicationFile(URL url)
0845 throws java.io.IOException {
0846 if(DEBUG) {
0847 System.out.println("Checking whether file is xml");
0848 }
0849 String firstLine;
0850 BufferedReader fileReader = null;
0851 try {
0852 fileReader = new BomStrippingInputStreamReader(url.openStream());
0853 firstLine = fileReader.readLine();
0854 } finally {
0855 if(fileReader != null) fileReader.close();
0856 }
0857 if(firstLine == null) {
0858 return false;
0859 }
0860 for(String startOfXml : STARTOFXMLAPPLICATIONFILES) {
0861 if(firstLine.length() >= startOfXml.length()
0862 && firstLine.substring(0, startOfXml.length()).equals(startOfXml)) {
0863 if(DEBUG) {
0864 System.out.println("isXMLApplicationFile = true");
0865 }
0866 return true;
0867 }
0868 }
0869 if(DEBUG) {
0870 System.out.println("isXMLApplicationFile = false");
0871 }
0872 return false;
0873 }
0874
0875 private static final String[] STARTOFXMLAPPLICATIONFILES = {
0876 "<gate.util.persistence.GateApplication>", "<?xml", "<!DOCTYPE"};
0877
0878 /**
0879 * @deprecated Use {@link #registerPersistentEquivalent(Class,Class)} instead
0880 */
0881 public static Class registerPersitentEquivalent(Class transientType,
0882 Class persistentType) throws PersistenceException {
0883 return registerPersistentEquivalent(transientType, persistentType);
0884 }
0885
0886 /**
0887 * Sets the persistent equivalent type to be used to (re)store a given
0888 * type of transient objects.
0889 *
0890 * @param transientType the type that will be replaced during
0891 * serialisation operations
0892 * @param persistentType the type used to replace objects of transient
0893 * type when serialising; this type needs to extend
0894 * {@link Persistence}.
0895 * @return the persitent type that was used before this mapping if
0896 * such existed.
0897 */
0898 public static Class registerPersistentEquivalent(Class transientType,
0899 Class persistentType) throws PersistenceException {
0900 if(!Persistence.class.isAssignableFrom(persistentType)) {
0901 throw new PersistenceException(
0902 "Persistent equivalent types have to implement "
0903 + Persistence.class.getName() + "!\n"
0904 + persistentType.getName() + " does not!");
0905 }
0906 return (Class)persistentReplacementTypes.put(transientType, persistentType);
0907 }
0908
0909 /**
0910 * A dictionary mapping from java type (Class) to the type (Class)
0911 * that can be used to store persistent data for the input type.
0912 */
0913 private static Map persistentReplacementTypes;
0914
0915 /**
0916 * Stores the persistent replacements created during a transaction in
0917 * order to avoid creating two different persistent copies for the
0918 * same object. The keys used are {@link ObjectHolder}s that contain
0919 * the transient values being converted to persistent equivalents.
0920 */
0921 private static ThreadLocal<LinkedList<Map>> existingPersistentReplacements;
0922
0923 /**
0924 * Stores the transient values obtained from persistent replacements
0925 * during a transaction in order to avoid creating two different
0926 * transient copies for the same persistent replacement. The keys used
0927 * are {@link ObjectHolder}s that hold persistent equivalents. The
0928 * values are the transient values created by the persisten
0929 * equivalents.
0930 */
0931 private static ThreadLocal<LinkedList<Map>> existingTransientValues;
0932
0933 private static ClassComparator classComparator = new ClassComparator();
0934
0935 /**
0936 * The file currently used to write the persisten representation. Will
0937 * only have a non-null value during storing operations.
0938 */
0939 static ThreadLocal<LinkedList<File>> persistenceFile;
0940
0941 /**
0942 * The URL currently used to read the persistent representation when
0943 * reading from a URL. Will only be non-null during restoring
0944 * operations.
0945 */
0946 static ThreadLocal<LinkedList<URL>> persistenceURL;
0947
0948 private static final class BooleanFlag {
0949 BooleanFlag(boolean initial) {
0950 flag = initial;
0951 }
0952 private boolean flag;
0953 public void setValue(boolean value) {
0954 flag = value;
0955 }
0956 public boolean getValue() {
0957 return flag;
0958 }
0959 }
0960
0961 private static ThreadLocal<LinkedList<Boolean>> useGateHome;
0962 private static ThreadLocal<LinkedList<Boolean>> warnAboutGateHome;
0963 private static ThreadLocal<LinkedList<BooleanFlag>> haveWarnedAboutGateHome;
0964
0965 static {
0966 persistentReplacementTypes = new HashMap();
0967 try {
0968 // VRs don't get saved, ....sorry guys :)
0969 registerPersistentEquivalent(VisualResource.class, SlashDevSlashNull.class);
0970
0971 registerPersistentEquivalent(URL.class, URLHolder.class);
0972
0973 registerPersistentEquivalent(Map.class, MapPersistence.class);
0974 registerPersistentEquivalent(Collection.class, CollectionPersistence.class);
0975
0976 registerPersistentEquivalent(ProcessingResource.class, PRPersistence.class);
0977
0978 registerPersistentEquivalent(DataStore.class, DSPersistence.class);
0979
0980 registerPersistentEquivalent(LanguageResource.class, LRPersistence.class);
0981
0982 registerPersistentEquivalent(Corpus.class, CorpusPersistence.class);
0983
0984 registerPersistentEquivalent(Controller.class, ControllerPersistence.class);
0985
0986 registerPersistentEquivalent(ConditionalController.class,
0987 ConditionalControllerPersistence.class);
0988
0989 registerPersistentEquivalent(ConditionalSerialAnalyserController.class,
0990 ConditionalSerialAnalyserControllerPersistence.class);
0991
0992 registerPersistentEquivalent(LanguageAnalyser.class,
0993 LanguageAnalyserPersistence.class);
0994
0995 registerPersistentEquivalent(SerialAnalyserController.class,
0996 SerialAnalyserControllerPersistence.class);
0997
0998 registerPersistentEquivalent(gate.persist.JDBCDataStore.class,
0999 JDBCDSPersistence.class);
1000
1001 registerPersistentEquivalent(gate.creole.AnalyserRunningStrategy.class,
1002 AnalyserRunningStrategyPersistence.class);
1003
1004 registerPersistentEquivalent(gate.creole.RunningStrategy.UnconditionalRunningStrategy.class,
1005 UnconditionalRunningStrategyPersistence.class);
1006 }
1007 catch(PersistenceException pe) {
1008 // builtins shouldn't raise this
1009 pe.printStackTrace();
1010 }
1011
1012 /**
1013 * Thread-local stack.
1014 */
1015 class ThreadLocalStack<T> extends ThreadLocal<LinkedList<T>> {
1016 @Override
1017 protected LinkedList<T> initialValue() {
1018 // TODO Auto-generated method stub
1019 return new LinkedList<T>();
1020 }
1021
1022 }
1023
1024 existingPersistentReplacements = new ThreadLocalStack<Map>();
1025 existingTransientValues = new ThreadLocalStack<Map>();
1026 persistenceFile = new ThreadLocalStack<File>();
1027 persistenceURL = new ThreadLocalStack<URL>();
1028 useGateHome = new ThreadLocalStack<Boolean>();
1029 warnAboutGateHome = new ThreadLocalStack<Boolean>();
1030 haveWarnedAboutGateHome = new ThreadLocalStack<BooleanFlag>();
1031 }
1032 }
|