All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gate.util.persistence.PersistenceManager Maven / Gradle / Ivy

Go to download

GATE - general achitecture for text engineering - is open source software capable of solving almost any text processing problem. This artifact enables you to embed the core GATE Embedded with its essential dependencies. You will able to use the GATE Embedded API and load and store GATE XML documents. This artifact is the perfect dependency for CREOLE plugins or for applications that need to customize the GATE dependencies due to confict with their own dependencies or for lower footprint.

The newest version!
/*
 *  Copyright (c) 1995-2012, The University of Sheffield. See the file
 *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
 *
 *  This file is part of GATE (see http://gate.ac.uk/), and is free
 *  software, licenced under the GNU Library General Public License,
 *  Version 2, June 1991 (in the distribution as file licence.html,
 *  and also available at http://gate.ac.uk/gate/licence.html).
 *
 *  Valentin Tablan 25/10/2001
 *
 *  $Id: PersistenceManager.java 20062 2017-02-02 13:00:32Z markagreenwood $
 *
 */
package gate.util.persistence;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Reader;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.reflection.FieldDictionary;
import com.thoughtworks.xstream.converters.reflection.SunUnsafeReflectionProvider;
import com.thoughtworks.xstream.converters.reflection.XStream12FieldKeySorter;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.QNameMap;
import com.thoughtworks.xstream.io.xml.StaxDriver;
import com.thoughtworks.xstream.io.xml.StaxReader;
import com.thoughtworks.xstream.io.xml.XStream11NameCoder;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;

import gate.Controller;
import gate.Corpus;
import gate.DataStore;
import gate.Gate;
import gate.LanguageAnalyser;
import gate.LanguageResource;
import gate.ProcessingResource;
import gate.VisualResource;
import gate.creole.ConditionalController;
import gate.creole.ConditionalSerialAnalyserController;
import gate.creole.Plugin;
import gate.creole.ResourceInstantiationException;
import gate.creole.ResourceReference;
import gate.creole.SerialAnalyserController;
import gate.event.ProgressListener;
import gate.event.StatusListener;
import gate.persist.GateAwareObjectInputStream;
import gate.persist.PersistenceException;
import gate.util.BomStrippingInputStreamReader;
import gate.util.Err;
import gate.util.Files;
import gate.util.GateException;
import gate.util.GateRuntimeException;
import gate.util.NameBearer;

/**
 * This class provides utility methods for saving resources through
 * serialisation via static methods.
 *
 * It now supports both native and xml serialization.
 */
public class PersistenceManager {

  private static final boolean DEBUG = false;

  private static final XStream xstream;

  /**
   * A reference to an object; it uses the identity hashcode and the
   * equals defined by object identity. These values will be used as
   * keys in the {link #existingPersistentReplacements} map.
   */
  static protected class ObjectHolder {
    ObjectHolder(Object target) {
      this.target = target;
    }

    @Override
    public int hashCode() {
      return System.identityHashCode(target);
    }

    @Override
    public boolean equals(Object obj) {
      if(obj instanceof ObjectHolder)
        return ((ObjectHolder)obj).target == this.target;
      else return false;
    }

    public Object getTarget() {
      return target;
    }

    private Object target;
  }// static class ObjectHolder{

  /**
   * This class is used as a marker for types that should NOT be
   * serialised when saving the state of a gate object. Registering this
   * type as the persistent equivalent for a specific class (via
   * {@link PersistenceManager#registerPersistentEquivalent(Class , Class)})
   * effectively stops all values of the specified type from being
   * serialised.
   *
   * Maps that contain values that should not be serialised will have
   * that entry removed. In any other places where such values occur
   * they will be replaced by null after deserialisation.
   */
  public static class SlashDevSlashNull implements Persistence {
    /**
     * Does nothing
     */
    @Override
    public void extractDataFromSource(Object source)
            throws PersistenceException {
    }

    /**
     * Returns null
     */
    @Override
    public Object createObject() throws PersistenceException,
            ResourceInstantiationException {
      return null;
    }

    static final long serialVersionUID = -8665414981783519937L;
  }
  
  public static class RRPersistence implements Persistence {

    private static final long serialVersionUID = 6543151074741224114L;

    String uriString;
    
    @Override
    public String toString() {
      return uriString;
    }

    @Override
    public void extractDataFromSource(Object source)
        throws PersistenceException {
      
      try {
        URI uri = null;
        
        if (source instanceof ResourceReference) {
          uri = ((ResourceReference)source).toURI();
        }
        
        if (uri == null) throw new UnsupportedOperationException("Whatever you are trying to persist isn't a URL");
        
        if(uri.getScheme() != null && uri.getScheme().equals("file")) {
            // url is what we want to convert to something that is relative to $relpath$, 
            // $resourceshome$ or $gatehome$ 
            
            // The file in which the URL should get persisted, the directory this file is in
            // is the location to which we want to make the URL relative, unless we want to 
            // use $gatehome$ or $resourceshome$
            File outFile = currentPersistenceFile();
            //File outFileReal = getCanonicalFileIfPossible(outFile);
            
            File urlFile = new File(uri);//Files.fileFromURL(url);
            //File urlFileReal = getCanonicalFileIfPossible(urlFile);
            
            //logger.debug("Trying to persist "+uri+" for "+outFile);
            
            uriString = URLHolder.relativize(outFile, urlFile);
            
        } // if protocol is file
        else {
          // protocol was not file:
          uriString = ((ResourceReference)source).toExternalForm();
        }
      }
      catch(ClassCastException | URISyntaxException e) {
        throw new PersistenceException(e);
      }
    }

    @Override
    public Object createObject()
        throws PersistenceException, ResourceInstantiationException {
      try {
        return new ResourceReference(URLHolder.unpackPersistentRepresentation(
            currentPersistenceURL().toURI(), uriString));
      } catch(URISyntaxException e) {
        throw new PersistenceException(e);
      }
    }
  }

  /**
   * URLs get upset when serialised and deserialised so we need to
   * convert them to strings for storage. 
   * In the case of
   * "file:" URLs the relative path to the persistence file
   * will actually be stored, except when {@link #saveObjectToFile(Object,File,boolean,boolean)} was
   * used and there is one of the following cases:
   * 
    *
  • when the URL refers to a resource * within the current GATE home directory and the persistence file is not within the GATE home * directory, in which case the relative path * to the GATE home directory will be stored. *
  • Otherwise, if the persistence file is not within the GATE directory and if the property * gate.user.resourceshome is set to a directory path and the URL refers * to a resource inside this directory, the relative path to this directory * will be stored. *
* If resources are stored relative to gate home or * resources home, a warning will also be logged. *

* The real path as returned by getCanonicalPath() is used to check if the resource is * within a special directory (e.g. GATE HOME). Once we know this, and the resource is in * a special directory, the canonical paths are used to generate the relative path. * If we are not inside a special directory, then the non-canonical path names will be used, so * that any symbolic links are NOT getting resolved, which is usually what you want. * The non-canonical path names will get normalized to remove things like "../dir" and the * result of the normalization is checked using getCanonicalPath() to see if it refers to * the same location as the non-normalized one (it could be different if the .. follows a * symbolic link, for example). If it is the same, then the normalized version is used, otherwise * the original version is used.

* */ public static class URLHolder implements Persistence { /** * Populates this Persistence with the data that needs to be stored * from the original source object. */ static final Logger logger = LoggerFactory.getLogger(URLHolder.class); public static String relativize(File outFile, File urlFile) throws URISyntaxException { File outFileReal = getCanonicalFileIfPossible(outFile); File urlFileReal = getCanonicalFileIfPossible(urlFile); // Note: the outDir will be correct if the application file itself is not a link // If the application file is a link than its parent is the parent of the real // path. File outDir = outFile.getParentFile(); File outDirReal = getCanonicalFileIfPossible(outDir); File outDirOfReal = getCanonicalFileIfPossible(outFileReal.getParentFile()); logger.debug("urlFile="+urlFile+", urlFileReal"+urlFileReal); logger.debug("outDir="+outDir+", outDirReal"+outDirReal); // by default, use just the relative path String pathMarker = relativePathMarker; // - this returns the canonical path to GATE_HOME File gateHomePathReal = getGateHomePath(); // get the canonical path of the resources home directory, if set, otherwise null File resourceshomeDirReal = getResourceshomePath(); // Only if we actually *want* to consider GATE_HOME and other special directories, // do all of this ... // Note: the semantics of this has slightly changed to what it was before: we only // do this is the use gatehome flag is set, not if it is not set but the warn flag // is set (previsouly, the warn flag alone was sufficient too). if(currentUseGateHome()) { // First of all, check if we want to use GATE_HOME instead: this happens // if the real location of the url is inside the real location of GATE_HOME // and the location of the outfile is outside of GATE_HOME if (!isContainedWithin(outFileReal, gateHomePathReal) && isContainedWithin(urlFileReal, gateHomePathReal)) { logger.debug("Setting path marker to "+gatehomePathMarker); if(currentWarnAboutGateHome()) { if(!currentHaveWarnedAboutGateHome().getValue()) { logger.warn( "\nYour application is using some of the resources/plugins "+ "distributed with GATE, and may not work as expected "+ "with different versions of GATE. You should consider "+ "making private local copies of the plug-ins, and "+ "distributing those with your application."); currentHaveWarnedAboutGateHome().setValue(true); } // the actual URL is shown every time //logger.warn("GATE resource referenced: "+url); } if(currentUseGateHome()) { pathMarker = gatehomePathMarker; } } else // Otherwise, do the same check for the resources home path, but only if it // is actuallys set if(resourceshomeDirReal != null && !isContainedWithin(outFileReal, resourceshomeDirReal) && isContainedWithin(urlFileReal, resourceshomeDirReal)) { if(currentWarnAboutGateHome()) { if(!currentHaveWarnedAboutResourceshome().getValue()) { logger.warn( "\nYour application is using resources from your project "+ "path at "+getResourceshomePath()+". Restoring the application "+ "will only work if the same project path is set."); currentHaveWarnedAboutResourceshome().setValue(true); } // the actual URL is shown every time //logger.warn("Resource referenced: "+url); } if(currentUseGateHome()) { pathMarker = resourceshomePathMarker; } } } String relPath = null; try { if(pathMarker.equals(relativePathMarker)) { // In theory we should just relativize here using the original paths, without // following any symbolic links. We do not want to follow symbolic links because // somebody may want to use a symbolic link to a completely different location // to include some resources into the project directory that contains the // pipeline (and the link to the resource). // However, if the project directory itself is within a linked location, then // GATE will sometimes use the linked and sometimes the non-linked path // (for some reason it uses the non-linked path in the plugin manager but it uses // the linked path when the application file is choosen). This means that // in those cases, there would be a large number of unwanted ../../../../some/dir/to/get/back // If we solve this by using the canonical paths, it will do the wrong thing for // links to resources. So we choose to make a compromise: if we can create a relative // path that does not generate any ../ at the beginning of the relative part, then // we use that, otherwise we use the real paths relPath = getRelativeFilePathString(outDir, urlFile); logger.debug("First relative path string attempt got " + relPath); if(relPath.startsWith("../")) { // if we want to actually use the real path, we have to be careful which is the // real parent of the out file: if outFile is a symbolic link then it is // outDirOfReal otherwise it is outDirReal logger.debug("upslength is " + upsLength(relPath)); String tmpPath = relPath; if(java.nio.file.Files.isSymbolicLink(outFile.toPath())) { tmpPath = getRelativeFilePathString(outDirOfReal, urlFileReal); logger.debug("trying outDirOfReal " + tmpPath); logger.debug("upslength is " + upsLength(tmpPath)); } else { tmpPath = getRelativeFilePathString(outDirReal, urlFileReal); logger.debug("trying outDirReal " + tmpPath); logger.debug("upslength is " + upsLength(tmpPath)); } if(upsLength(tmpPath) < upsLength(relPath)) { relPath = tmpPath; } logger.debug("Using tmpPath " + relPath); } // if we still get something that starts with ../ then our only remaining option is // to find if a parent } else if(pathMarker.equals(gatehomePathMarker)) { relPath = getRelativeFilePathString(gateHomePathReal, urlFileReal); } else if(pathMarker.equals(resourceshomePathMarker)) { relPath = getRelativeFilePathString(resourceshomeDirReal, urlFileReal); } else { // this should really never happen! throw new GateRuntimeException("Unexpected error when persisting URL " + urlFile); } } catch(IllegalArgumentException e) { logger.warn("Could not convert " + urlFile + " to a relative path - storing as absolute", e); return urlFile.toURI().toString(); } return pathMarker + relPath; } @Override public void extractDataFromSource(Object source) throws PersistenceException { try { URL url = null; if (source instanceof URL) { url = (URL)source; } else if (source instanceof Plugin.Directory) { url = ((Plugin.Directory)source).getBaseURL(); } if (url == null) throw new UnsupportedOperationException("Whatever you are trying to persist isn't a URL"); if(url.getProtocol().equals("file")) { // url is what we want to convert to something that is relative to $relpath$, // $resourceshome$ or $gatehome$ // The file in which the URL should get persisted, the directory this file is in // is the location to which we want to make the URL relative, unless we want to // use $gatehome$ or $resourceshome$ File outFile = currentPersistenceFile(); //File outFileReal = getCanonicalFileIfPossible(outFile); File urlFile = Files.fileFromURL(url); //File urlFileReal = getCanonicalFileIfPossible(urlFile); logger.debug("Trying to persist "+url+" for "+outFile); urlString = relativize(outFile, urlFile); } // if protocol is file else { // protocol was not file: urlString = ((URL)source).toExternalForm(); } } catch(ClassCastException | URISyntaxException e) { throw new PersistenceException(e); } } /** * Creates a new object from the data contained. This new object is * supposed to be a copy for the original object used as source for * data extraction. */ @Override public Object createObject() throws PersistenceException { try { return unpackPersistentRepresentation( currentPersistenceURL().toURL().toURI(), urlString).toURL(); } catch(URISyntaxException | IOException e1) { throw new PersistenceException(e1); } } public static URI unpackPersistentRepresentation(URI context, String persistent) throws PersistenceException { try { if(persistent.startsWith(relativePathMarker)) { // If the part after the $relpath$ marker is empty, the normal method // would get us the URL of the context which will be the URL of the pipeline, not // of the directory where the pipeline is located. // If the part after the $relpath$ marker starts with a slash, the normal method // would cause the $relpath$ marker to get ignored, but what we really want is probably // that the slash gets ignored and the part that follows be appended to the // directory part of the context. // If the relative part is empty, we make an attempt to remove the last part of the // URL path from context and use that as the parent/context instead // If the relative part is starting with a slash, we remove that //URL ret = new URL(context, urlString.substring(relativePathMarker // .length())); URI ret = combineContextAndRelative(context, persistent.substring(relativePathMarker .length()), true); logger.debug("CurrentPresistenceURL is "+context+" created="+ret); return ret; } else if(persistent.startsWith(gatehomePathMarker)) { throw new GateRuntimeException("$gatehome$ variable no longer supported, please upgrade your application"); } else if(persistent.startsWith(gatepluginsPathMarker)) { throw new GateRuntimeException("$gateplugins$ variable no longer supported, please upgrade your application"); } else if(persistent.startsWith(resourceshomePathMarker)) { if(getResourceshomePath() == null) { throw new GateRuntimeException("Cannot restore URL "+persistent+ "property "+resourceshomePropertyName+" is not set"); } URL resourceshomeurl = getResourceshomePath().toURI().toURL(); //return new URL(resourceshomeurl, urlString.substring(resourceshomePathMarker.length())); return combineContextAndRelative(resourceshomeurl.toURI(), persistent.substring(resourceshomePathMarker.length()),false); } else if(persistent.startsWith(syspropMarker)) { String urlRestString = persistent.substring(syspropMarker.length()); int dollarindex = urlRestString.indexOf("$"); if(dollarindex > 0) { String syspropname = urlRestString.substring(0,dollarindex); String propvalue = System.getProperty(syspropname); if(propvalue == null) { throw new PersistenceException("Property '"+syspropname+"' is null in "+persistent); } // TODO: we should only assume a file if what we get does not start with a protocol URI propuri = (new File(propvalue)).toURI(); if(dollarindex == urlRestString.length()) { return propuri; } else { //return new URL(propuri, urlRestString.substring(dollarindex+1)); return combineContextAndRelative(propuri, urlRestString.substring(dollarindex+1),false); } } else if(dollarindex == 0) { throw new PersistenceException("No property name after '"+syspropMarker+"' in "+persistent); } else { throw new PersistenceException("No ending $ after '"+syspropMarker+"' in "+persistent); } } else { return new URI(persistent); } } catch(MalformedURLException | URISyntaxException mue) { throw new PersistenceException(mue); } } String urlString; //******** Helper methods just used inside the URLHolder class public static URI combineContextAndRelative(URI context, String relative, boolean contextIsFile) { // make sure we always have a proper string if(relative==null) relative = ""; // we always want to interpret the part after the marker as a relative path, so // remove any starting slash (for performance reasons, only one is removed, so // an absolute path could get hacked by using two slashes) if(relative.startsWith("/")) relative = relative.substring(1); // if the relative path is empty, return the directory part of the context (if contextIsFile // is true, then the parent, otherwise the context unchanged) if(relative.isEmpty()) { if(!contextIsFile) return context; // context is file, get the parent and return that /*URI tmpUri; try { tmpUri = context.toURI(); } catch (URISyntaxException ex) { throw new GateRuntimeException("Could not convert context URL to URI: "+context,ex); } // find the parent: in our use cases, the context should always be a file tmpUri = tmpUri.resolve("."); try { context = tmpUri.toURL(); } catch (Exception ex) { throw new GateRuntimeException("Could not convert context URI to URL: "+tmpUri,ex); }*/ return context.resolve("."); } else { // use the URL constructor to splice the two parts together if (context.isOpaque()) { URL tmpUrl; try { tmpUrl = new URL(context.toURL(),relative); return tmpUrl.toURI(); } catch (Exception ex) { throw new GateRuntimeException("Could not create a URL from context "+context+" and relative part "+relative,ex); } } return context.resolve(relative).normalize(); } } private static File getGateHomePath() { if(gatehomePath != null) { return gatehomePath; } else { gatehomePath = getCanonicalFileIfPossible(Gate.getGateHome()); return gatehomePath; } } private static File getResourceshomePath() { if(haveResourceshomePath == null) { String resourceshomeString = System.getProperty(resourceshomePropertyName); if(resourceshomeString == null) { haveResourceshomePath = false; return null; } resourceshomePath = new File(resourceshomeString); resourceshomePath = getCanonicalFileIfPossible(resourceshomePath); haveResourceshomePath = true; return resourceshomePath; } else if(haveResourceshomePath) { return resourceshomePath; } else { return null; } } private static File getCanonicalFileIfPossible(File file) { if (file == null) return file; File tmp = file; try { tmp = tmp.getCanonicalFile(); } catch (IOException ex) { // ignore } return tmp; } /** * Creates the string that needs to get appended to the path of dir to obtain the path * to file. * This does NOT follow any symbolic links - if true locations should be used, they * have to get passed to this method. This will make an attempt to access the file system * to see of normalization can be done without changing the meaning of the path, but if * accessing the file system fails, this method will silently use the non-normalized * path instead. * @param dir the directory starting from * @param file the file we want to reference * @return the path appended to dir to get to file */ private static String getRelativeFilePathString(File dir, File file) { Path dirPath = dir.toPath(); Path filePath = file.toPath(); try { // create a normalized version of the paths, but without following symbolic links dirPath = dirPath.toRealPath(LinkOption.NOFOLLOW_LINKS); } catch (IOException ex) { // ignore and use original } try { filePath = filePath.toRealPath(LinkOption.NOFOLLOW_LINKS); } catch (IOException ex) { // ignore and use original } // turn both into URIs and try to relativise one against the other URI dirUri = dirPath.toUri(); URI fileUri = filePath.toUri(); return getRelativeUri(dirUri, fileUri); } private static int upsLength(String path) { Matcher m = goUpPattern.matcher(path); if(m.matches()) { return m.group(1).length(); } else { return 0; } } public static String getRelativeUri(URI context, URI target) { if(!Objects.equals(context.getHost(), target.getHost())) { // this typically only happens on Windows when one path is a UNC path // (\\server\share\something) and the other is not, or both are UNCs // on different servers throw new IllegalArgumentException("Can't create relative path between " + context + " and " + target + " as they are on different hosts."); } String contextPath = context.getRawPath(); String targetPath = target.getRawPath(); String[] targetComponents = targetPath.split("/", -1); String[] contextComponents = contextPath.split("/", -1); // find the length of the common prefix (which may be zero) int commonPathElements = 0; while(commonPathElements < targetComponents.length && commonPathElements < contextComponents.length && Objects.equals(targetComponents[commonPathElements], contextComponents[commonPathElements])) { commonPathElements++; } // construct the relative path by stripping the common prefix from both // paths, then concatenating enough ".." to get from the context to the // common prefix point, then the target path from there return Stream.concat(IntStream.range(commonPathElements, contextComponents.length - 1).mapToObj((i) -> ".."), IntStream.range(commonPathElements, targetComponents.length).mapToObj((i) -> targetComponents[i])) .collect(Collectors.joining("/")); } /** * This string will be used to start the serialisation of URL that * represent relative paths. */ private static final String relativePathMarker = "$relpath$"; private static final String gatehomePathMarker = "$gatehome$"; private static final String gatepluginsPathMarker = "$gateplugins$"; private static final String syspropMarker = "$sysprop:"; private static final String resourceshomePathMarker = "$resourceshome$"; private static final String resourceshomePropertyName = "gate.user.resourceshome"; // After initialisation this is either the canonical path to the project // home as set by the property resourceshomePropertyName or null if the // property has not been set. private static File resourceshomePath = null; private static Boolean haveResourceshomePath = null; // The canoncial gate home path gets cached in this field private static File gatehomePath = null; static final long serialVersionUID = 7943459208429026229L; private static final Pattern goUpPattern = Pattern.compile( "^((?:\\.\\."+Pattern.quote(FileSystems.getDefault().getSeparator())+")+).*"); } // class URLHolder public static class ClassComparator implements Comparator>, Serializable { private static final long serialVersionUID = 6632907018933862733L; /** * Compares two {@link Class} values in terms of specificity; the * more specific class is said to be "smaller" than the * more generic one hence the {@link Object} class is the * "largest" possible class. When two classes are not * comparable (i.e. not assignable from each other) in either * direction a NotComparableException will be thrown. both input * objects should be Class values otherwise a * {@link ClassCastException} will be thrown. * */ @Override public int compare(Class c1, Class c2) { if(c1.equals(c2)) return 0; if(c1.isAssignableFrom(c2)) return 1; if(c2.isAssignableFrom(c1)) return -1; throw new NotComparableException(); } } /** * Thrown by a comparator when the values provided for comparison are * not comparable. */ @SuppressWarnings("serial") public static class NotComparableException extends RuntimeException { public NotComparableException(String message) { super(message); } public NotComparableException() { } } /** * Recursively traverses the provided object and replaces it and all * its contents with the appropriate persistent equivalent classes. * * @param target the object to be analysed and translated into a * persistent equivalent. * @return the persistent equivalent value for the provided target */ public static Serializable getPersistentRepresentation(Object target) throws PersistenceException { if(target == null) return null; // first check we don't have it already Persistence res = existingPersistentReplacements .get().getFirst().get(new ObjectHolder(target)); if(res != null) return res; Class type = target.getClass(); Class newType = getMostSpecificPersistentType(type); if(newType == null) { // no special handler if(target instanceof Serializable) return (Serializable)target; else throw new PersistenceException( "Could not find a serialisable replacement for " + type); } // we have a new type; create the new object, populate and return it try { res = (Persistence)newType.newInstance(); } catch(Exception e) { throw new PersistenceException(e); } if(target instanceof NameBearer) { StatusListener sListener = (StatusListener)Gate.getListeners().get( "gate.event.StatusListener"); if(sListener != null) { sListener.statusChanged("Storing " + ((NameBearer)target).getName()); } } res.extractDataFromSource(target); existingPersistentReplacements.get().getFirst().put(new ObjectHolder(target), res); return res; } public static Object getTransientRepresentation(Object target) throws PersistenceException, ResourceInstantiationException { return getTransientRepresentation(target,null,null); } public static Object getTransientRepresentation(Object target, String containingControllerName, Map> initParamOverrides) throws PersistenceException, ResourceInstantiationException { if(target == null || target instanceof SlashDevSlashNull) return null; if(target instanceof Persistence) { ObjectHolder resultKey = new ObjectHolder(target); // check the cached values; maybe we have the result already Object result = existingTransientValues.get().getFirst().get(resultKey); if(result != null) return result; // we didn't find the value: create it if(containingControllerName != null && target instanceof AbstractPersistence) { ((AbstractPersistence)target).containingControllerName = containingControllerName; ((AbstractPersistence)target).initParamOverrides = initParamOverrides; } result = ((Persistence)target).createObject(); existingTransientValues.get().getFirst().put(resultKey, result); return result; } else return target; } /** * Finds the most specific persistent replacement type for a given * class. Look for a type that has a registered persistent equivalent * starting from the provided class continuing with its superclass and * implemented interfaces and their superclasses and implemented * interfaces and so on until a type is found. Classes are considered * to be more specific than interfaces and in situations of ambiguity * the most specific types are considered to be the ones that don't * belong to either java or GATE followed by the ones that belong to * GATE and followed by the ones that belong to java. * * E.g. if there are registered persistent types for * {@link gate.Resource} and for {@link gate.LanguageResource} than * such a request for a {@link gate.Document} will yield the * registered type for {@link gate.LanguageResource}. */ protected static Class getMostSpecificPersistentType(Class type) { // this list will contain all the types we need to expand to // superclass + // implemented interfaces. We start with the provided type and work // our way // up the ISA hierarchy List> expansionSet = new ArrayList>(); expansionSet.add(type); // algorithm: // 1) check the current expansion set // 2) expand the expansion set // at each expansion stage we'll have a class and three lists of // interfaces: // the user defined ones; the GATE ones and the java ones. List> userInterfaces = new ArrayList>(); List> gateInterfaces = new ArrayList>(); List> javaInterfaces = new ArrayList>(); while(!expansionSet.isEmpty()) { // 1) check the current set Iterator> typesIter = expansionSet.iterator(); while(typesIter.hasNext()) { Class result = persistentReplacementTypes.get(typesIter.next()); if(result != null) { return result; } } // 2) expand the current expansion set; // the expanded expansion set will need to be ordered according to // the // rules (class >> interface; user interf >> gate interf >> java // interf) // at each point we only have at most one class if(type != null) type = type.getSuperclass(); userInterfaces.clear(); gateInterfaces.clear(); javaInterfaces.clear(); typesIter = expansionSet.iterator(); while(typesIter.hasNext()) { Class aType = typesIter.next(); Class[] interfaces = aType.getInterfaces(); // distribute them according to their type for(int i = 0; i < interfaces.length; i++) { Class anIterf = interfaces[i]; String interfType = anIterf.getName(); if(interfType.startsWith("java")) { javaInterfaces.add(anIterf); } else if(interfType.startsWith("gate")) { gateInterfaces.add(anIterf); } else userInterfaces.add(anIterf); } } expansionSet.clear(); if(type != null) expansionSet.add(type); expansionSet.addAll(userInterfaces); expansionSet.addAll(gateInterfaces); expansionSet.addAll(javaInterfaces); } // we got out the while loop without finding anything; return null; return null; } /** * This method can be used to determine if a specified file (or directory) is * contained within a given directory. * * This will check if the real location of file (with all symbolic links removed) is within * the real location of directory with all symbolic links removed. If part of the file path * is within the directory but eventually a symbolic link leads out of that directory, the * file is considered to NOT be inside the directory. *

* NOTE: this accesses the file system to find the real locations for file and directory. * However, if there is a problem accessing the file system, the method will fall back to * using the literal path names for checking and NOT produce an error. This is done so that * any saving operation during which this method is invoked is not aborted and proceeds, * creating a file that may be correct apart from one or more serialized URLs. * * @param file * is this file contained within * @param directory * this directory * @return true if the file is contained within the directory, false otherwise */ public static boolean isContainedWithin(File file, File directory) { // JP: new experimental solution using java.nio Path dir = directory.toPath(); try { dir = dir.toRealPath(); } catch (IOException ex) { // ignore and use the original } Path filePath = file.toPath(); try { filePath = filePath.toRealPath(); } catch (IOException ex) { // ignore and use the original } return filePath.startsWith(dir); // JP: Old solution using java.io and walking the tree and getCanonicalFile /* File dir = directory; try { dir = directory.getCanonicalFile(); } catch (IOException ex) { // ignore } if(!dir.isDirectory()) { return false; } File parent = file.getParentFile(); while (parent != null) { try { parent = parent.getCanonicalFile(); } catch (IOException ex) { // ignore } if (parent.equals(dir)) return true; parent = parent.getParentFile(); } return false; */ } /** * Calculates the relative path for a file: URL starting from a given * context which is also a file: URL. * * @param context the URL to be used as context. * @param target the URL for which the relative path is computed. * @return a String value representing the relative path. Constructing * a URL from the context URL and the relative path should * result in the target URL. */ public static String getRelativePath(URL context, URL target) { try { return URLHolder.getRelativeUri(context.toURI(), target.toURI()); } catch(URISyntaxException e) { throw new GateRuntimeException("Unable to construct relative path between " + context + " and " + target); } } /** * Save the given object to the file, without using $gatehome$. * This is equivalent to {@link #saveObjectToFile(Object,File,boolean,boolean)} with the * third and fourth parameter set to false. * This method exists with this definition to stay backwards compatible with * code that was using this method before paths relative to $gatehome$ were supported. * @param obj The object to persist * @param file The file where to persists to * @throws PersistenceException * @throws IOException */ public static void saveObjectToFile(Object obj, File file) throws PersistenceException, IOException { saveObjectToFile(obj, file, false, false); } /** * Save the given object to the given file. * * @param obj The object to persist. * @param file The file where to persist to * @param usegatehome if true (recommended) use $gatehome$ and $resourceshome$ instead of * $relpath$ in any saved path URLs if the location of that URL is inside GATE home or * inside the resources home directory (if set). * @param warnaboutgatehome if true, issue a warning message when a saved URL uses $gatehome$ * or $resourceshome$. * @throws PersistenceException * @throws IOException */ public static void saveObjectToFile(Object obj, File file, boolean usegatehome, boolean warnaboutgatehome) throws PersistenceException, IOException { ProgressListener pListener = (ProgressListener)Gate.getListeners() .get("gate.event.ProgressListener"); StatusListener sListener = (gate.event.StatusListener)Gate .getListeners().get("gate.event.StatusListener"); long startTime = System.currentTimeMillis(); if(pListener != null) pListener.progressChanged(0); // The object output stream is used for native serialization, // but the xstream and filewriter are used for XML serialization. ObjectOutputStream oos = null; HierarchicalStreamWriter writer = null; warnAboutGateHome.get().addFirst(warnaboutgatehome); useGateHome.get().addFirst(usegatehome); startPersistingTo(file); try { if(Gate.getUseXMLSerialization()) { // Just create the filewriter that will later be // used to serialize objects. FileWriter fileWriter = new FileWriter(file); writer = new PrettyPrintWriter(fileWriter, new XmlFriendlyNameCoder("-", "_")); } else { oos = new ObjectOutputStream(new FileOutputStream(file)); } Object persistentList = getPersistentRepresentation(new ArrayList(Gate.getCreoleRegister().getPlugins())); Object persistentObject = getPersistentRepresentation(obj); if(Gate.getUseXMLSerialization()) { // We need to put the urls and the application itself together // as xstreams can only hold one object. GateApplication gateApplication = new GateApplication(); //gateApplication.workspace = new File("cache"); gateApplication.urlList = persistentList; gateApplication.application = persistentObject; // Then do the actual serialization. xstream.marshal(gateApplication, writer); } else { // This is for native serialization. oos.writeObject(persistentList); // now write the object oos.writeObject(persistentObject); } } finally { finishedPersisting(); if(oos != null) { oos.flush(); oos.close(); } if(writer != null) { // Just make sure that all the xml is written, and the file // closed. writer.flush(); writer.close(); } long endTime = System.currentTimeMillis(); if(sListener != null) sListener.statusChanged("Storing completed in " + NumberFormat.getInstance().format( (double)(endTime - startTime) / 1000) + " seconds"); if(pListener != null) pListener.processFinished(); } } /** * Set up the thread-local state for a new persistence run. */ private static void startPersistingTo(File file) { haveWarnedAboutGateHome.get().addFirst(new BooleanFlag(false)); haveWarnedAboutResourceshome.get().addFirst(new BooleanFlag(false)); persistenceFile.get().addFirst(file); existingPersistentReplacements.get().addFirst(new HashMap()); } /** * Get the file currently being saved by this thread. */ public static File currentPersistenceFile() { return persistenceFile.get().getFirst(); } public static List currentPersistenceFileStack() { return Collections.unmodifiableList(persistenceFile.get()); } private static Boolean currentWarnAboutGateHome() { return warnAboutGateHome.get().getFirst(); } private static Boolean currentUseGateHome() { //return useGateHome.get().getFirst(); //TODO remove this functionality as it no longer makes any sense return false; } private static BooleanFlag currentHaveWarnedAboutGateHome() { return haveWarnedAboutGateHome.get().getFirst(); } private static BooleanFlag currentHaveWarnedAboutResourceshome() { return haveWarnedAboutResourceshome.get().getFirst(); } /** * Clean up the thread-local state for the current persistence run. */ private static void finishedPersisting() { persistenceFile.get().removeFirst(); if(persistenceFile.get().isEmpty()) { persistenceFile.remove(); } existingPersistentReplacements.get().removeFirst(); if(existingPersistentReplacements.get().isEmpty()) { existingPersistentReplacements.remove(); } } public static Object loadObjectFromFile(File file) throws PersistenceException, IOException, ResourceInstantiationException { try { return loadObjectFromUri(file.toURI()); } catch (URISyntaxException e) { throw new PersistenceException(e); } } public static Object loadObjectFromUrl(URL url) throws PersistenceException, IOException, ResourceInstantiationException { try { return loadObjectFromUri(url.toURI()); } catch(URISyntaxException e) { throw new PersistenceException(e); } } public static Object loadObjectFromUri(URI uri) throws PersistenceException, IOException, ResourceInstantiationException, URISyntaxException { ResourceReference resourceReference = new ResourceReference(uri); if(!Gate.isInitialised()) throw new ResourceInstantiationException( "You must call Gate.init() before you can restore resources"); ProgressListener pListener = (ProgressListener)Gate.getListeners() .get("gate.event.ProgressListener"); StatusListener sListener = (gate.event.StatusListener)Gate .getListeners().get("gate.event.StatusListener"); if(pListener != null) pListener.progressChanged(0); startLoadingFrom(resourceReference); //the actual stream obtained from the URL. We keep a reference to this //so we can ensure it gets closed. InputStream rawStream = null; try { long startTime = System.currentTimeMillis(); // Determine whether the file contains an application serialized in // xml // format. Otherwise we will assume that it contains native // serializations. boolean xmlStream = isXmlApplicationFile(resourceReference.toURL()); ObjectInputStream ois = null; HierarchicalStreamReader reader = null; // Make the appropriate kind of streams that will be used, depending // on // whether serialization is native or xml. if(xmlStream) { // we don't want to strip the BOM on XML. Reader inputReader = new InputStreamReader( rawStream = resourceReference.openStream()); try { XMLInputFactory inputFactory = XMLInputFactory.newInstance(); inputFactory.setProperty(XMLInputFactory.IS_COALESCING, true); XMLStreamReader xsr = inputFactory.createXMLStreamReader( resourceReference.toURL().toExternalForm(), inputReader); reader = new StaxReader(new QNameMap(), xsr); } catch(XMLStreamException xse) { // make sure the stream is closed, on error inputReader.close(); throw new PersistenceException("Error creating reader", xse); } // make the XML stream appear as a normal ObjectInputStream ois = xstream.createObjectInputStream(reader); } else { // use GateAwareObjectInputStream to load classes through the // GATE ClassLoader if they can't be loaded through the one // ObjectInputStream would normally use ois = new GateAwareObjectInputStream(resourceReference.openStream()); } Object res = null; try { // first read the list of creole URLs. @SuppressWarnings("unchecked") Iterator urlIter = ((Collection)getTransientRepresentation(ois.readObject())) .iterator(); // and re-register them while(urlIter.hasNext()) { Object anUrl = urlIter.next(); try { if (anUrl instanceof URL) Gate.getCreoleRegister().registerPlugin(new Plugin.Directory((URL)anUrl)); else if (anUrl instanceof Plugin) Gate.getCreoleRegister().registerPlugin((Plugin)anUrl); } catch(GateException ge) { System.out.println("We've hit an error!"); ge.printStackTrace(); ge.printStackTrace(Err.getPrintWriter()); Err.prln("Could not reload creole directory "+ anUrl); } } // now we can read the saved object in the presence of all // the right plugins res = ois.readObject(); // ensure a fresh start clearCurrentTransients(); res = getTransientRepresentation(res); long endTime = System.currentTimeMillis(); if(sListener != null) sListener.statusChanged("Loading completed in " + NumberFormat.getInstance().format( (double)(endTime - startTime) / 1000) + " seconds"); return res; } catch(ResourceInstantiationException rie) { if(sListener != null) sListener.statusChanged( "Failure during instantiation of resources."); throw rie; } catch(PersistenceException pe) { if(sListener != null) sListener.statusChanged( "Failure during persistence operations."); throw pe; } catch(Exception ex) { if(sListener != null) sListener.statusChanged("Loading failed!"); throw new PersistenceException(ex); } finally { //make sure the stream gets closed if (ois != null) ois.close(); if(reader != null) reader.close(); } } finally { if(rawStream != null) rawStream.close(); finishedLoading(); if(pListener != null) pListener.processFinished(); } } /** * Set up the thread-local state for the current loading run. */ private static void startLoadingFrom(ResourceReference rr) { persistenceURL.get().addFirst(rr); existingTransientValues.get().addFirst(new HashMap()); } /** * Clear the current list of transient replacements without * popping them off the stack. */ private static void clearCurrentTransients() { existingTransientValues.get().getFirst().clear(); } /** * Get the URL currently being loaded by this thread. */ public static ResourceReference currentPersistenceURL() { return persistenceURL.get().getFirst(); } public static List currentPersistenceURLStack() { return Collections.unmodifiableList(persistenceURL.get()); } /** * Clean up the thread-local state at the end of a loading run. */ private static void finishedLoading() { persistenceURL.get().removeFirst(); if(persistenceURL.get().isEmpty()) { persistenceURL.remove(); } existingTransientValues.get().removeFirst(); if(existingTransientValues.get().isEmpty()) { existingTransientValues.remove(); } } /** * Determine whether the URL contains a GATE application serialized * using XML. * * @param url The URL to check. * @return true if the URL refers to an xml serialized application, * false otherwise. */ private static boolean isXmlApplicationFile(URL url) throws java.io.IOException { if(DEBUG) { System.out.println("Checking whether file is xml"); } String firstLine; BufferedReader fileReader = null; try { fileReader = new BomStrippingInputStreamReader(url.openStream()); firstLine = fileReader.readLine(); } finally { if(fileReader != null) fileReader.close(); } if(firstLine == null) { return false; } for(String startOfXml : STARTOFXMLAPPLICATIONFILES) { if(firstLine.length() >= startOfXml.length() && firstLine.substring(0, startOfXml.length()).equals(startOfXml)) { if(DEBUG) { System.out.println("isXMLApplicationFile = true"); } return true; } } if(DEBUG) { System.out.println("isXMLApplicationFile = false"); } return false; } private static final String[] STARTOFXMLAPPLICATIONFILES = { "", " registerPersistentEquivalent(Class transientType, Class persistentType) throws PersistenceException { if(!Persistence.class.isAssignableFrom(persistentType)) { throw new PersistenceException( "Persistent equivalent types have to implement " + Persistence.class.getName() + "!\n" + persistentType.getName() + " does not!"); } return persistentReplacementTypes.put(transientType, persistentType); } /** * A dictionary mapping from java type (Class) to the type (Class) * that can be used to store persistent data for the input type. */ private static Map,Class> persistentReplacementTypes; /** * Stores the persistent replacements created during a transaction in * order to avoid creating two different persistent copies for the * same object. The keys used are {@link ObjectHolder}s that contain * the transient values being converted to persistent equivalents. */ private static ThreadLocal>> existingPersistentReplacements; /** * Stores the transient values obtained from persistent replacements * during a transaction in order to avoid creating two different * transient copies for the same persistent replacement. The keys used * are {@link ObjectHolder}s that hold persistent equivalents. The * values are the transient values created by the persistence * equivalents. */ private static ThreadLocal>> existingTransientValues; //private static ClassComparator classComparator = new ClassComparator(); /** * The file currently used to write the persisten representation. Will * only have a non-null value during storing operations. */ static ThreadLocal> persistenceFile; /** * The URL currently used to read the persistent representation when * reading from a URL. Will only be non-null during restoring * operations. */ static ThreadLocal> persistenceURL; private static final class BooleanFlag { BooleanFlag(boolean initial) { flag = initial; } private boolean flag; public void setValue(boolean value) { flag = value; } public boolean getValue() { return flag; } } private static ThreadLocal> useGateHome; private static ThreadLocal> warnAboutGateHome; private static ThreadLocal> haveWarnedAboutGateHome; private static ThreadLocal> haveWarnedAboutResourceshome; static { xstream = new XStream(new SunUnsafeReflectionProvider(new FieldDictionary(new XStream12FieldKeySorter())), new StaxDriver(new XStream11NameCoder())) { @Override protected boolean useXStream11XmlFriendlyMapper() { return true; } }; // make XStream load classes through the GATE ClassLoader xstream.setClassLoader(Gate.getClassLoader()); Gate.configureXStreamSecurity(xstream); persistentReplacementTypes = new HashMap, Class>(); try { // VRs don't get saved, ....sorry guys :) registerPersistentEquivalent(VisualResource.class, SlashDevSlashNull.class); registerPersistentEquivalent(URL.class, URLHolder.class); registerPersistentEquivalent(ResourceReference.class, RRPersistence.class); //for compatibility reasons we treat Plugins loaded from a directory URL as a URL registerPersistentEquivalent(Plugin.Directory.class, URLHolder.class); //we've never supported saving registered components as plugins and we aren't about to start now registerPersistentEquivalent(Plugin.Component.class, SlashDevSlashNull.class); registerPersistentEquivalent(Map.class, MapPersistence.class); registerPersistentEquivalent(Collection.class, CollectionPersistence.class); registerPersistentEquivalent(ProcessingResource.class, PRPersistence.class); registerPersistentEquivalent(DataStore.class, DSPersistence.class); registerPersistentEquivalent(LanguageResource.class, LRPersistence.class); registerPersistentEquivalent(Corpus.class, CorpusPersistence.class); registerPersistentEquivalent(Controller.class, ControllerPersistence.class); registerPersistentEquivalent(ConditionalController.class, ConditionalControllerPersistence.class); registerPersistentEquivalent(ConditionalSerialAnalyserController.class, ConditionalSerialAnalyserControllerPersistence.class); registerPersistentEquivalent(LanguageAnalyser.class, LanguageAnalyserPersistence.class); registerPersistentEquivalent(SerialAnalyserController.class, SerialAnalyserControllerPersistence.class); registerPersistentEquivalent(gate.creole.AnalyserRunningStrategy.class, AnalyserRunningStrategyPersistence.class); registerPersistentEquivalent(gate.creole.RunningStrategy.UnconditionalRunningStrategy.class, UnconditionalRunningStrategyPersistence.class); } catch(PersistenceException pe) { // builtins shouldn't raise this pe.printStackTrace(); } /** * Thread-local stack. */ class ThreadLocalStack extends ThreadLocal> { @Override protected LinkedList initialValue() { return new LinkedList(); } } existingPersistentReplacements = new ThreadLocalStack>(); existingTransientValues = new ThreadLocalStack>(); persistenceFile = new ThreadLocalStack(); persistenceURL = new ThreadLocalStack(); useGateHome = new ThreadLocalStack(); warnAboutGateHome = new ThreadLocalStack(); haveWarnedAboutGateHome = new ThreadLocalStack(); haveWarnedAboutResourceshome = new ThreadLocalStack(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy