Merge pull request #10 from apache/feature/5808-destroy-auto-created-resource-managers-if-feasible-v2

[UIMA-5808] Destroy auto-created resource managers if feasible
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/component/initialize/ConfigurationParameterInitializer.java b/uimafit-core/src/main/java/org/apache/uima/fit/component/initialize/ConfigurationParameterInitializer.java
index 1b2f3ba..059b169 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/component/initialize/ConfigurationParameterInitializer.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/component/initialize/ConfigurationParameterInitializer.java
@@ -37,7 +37,9 @@
 import org.apache.uima.resource.CustomResourceSpecifier;
 import org.apache.uima.resource.DataResource;
 import org.apache.uima.resource.Parameter;
+import org.apache.uima.resource.Resource;
 import org.apache.uima.resource.ResourceInitializationException;
+import org.apache.uima.resource.ResourceManager;
 import org.apache.uima.resource.ResourceSpecifier;
 import org.apache.uima.resource.metadata.ConfigurationParameterSettings;
 import org.apache.uima.resource.metadata.NameValuePair;
@@ -193,13 +195,23 @@
    */
   public static void initialize(final Object component, final Map<String, Object> map)
           throws ResourceInitializationException {
-    UimaContextAdmin context = UIMAFramework.newUimaContext(UIMAFramework.getLogger(),
-            ResourceManagerFactory.newResourceManager(), UIMAFramework.newConfigurationManager());
+    // If there is already a UimaContext then re-use that, otherwise create a new one.
+    UimaContextAdmin context;
+    if (component instanceof Resource) {
+      context = ((Resource) component).getUimaContextAdmin();
+    } else {
+      ResourceManager resMgr = ResourceManagerFactory.newResourceManager();
+      context = UIMAFramework.newUimaContext(UIMAFramework.getLogger(), resMgr,
+              UIMAFramework.newConfigurationManager());
+    }
+
     ConfigurationManager cfgMgr = context.getConfigurationManager();
     cfgMgr.setSession(context.getSession());
+    
     for (Entry<String, Object> e : map.entrySet()) {
       cfgMgr.setConfigParameterValue(context.getQualifiedContextName() + e.getKey(), e.getValue());
     }
+
     initialize(component, context);
   }
 
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/factory/CollectionReaderFactory.java b/uimafit-core/src/main/java/org/apache/uima/fit/factory/CollectionReaderFactory.java
index 2ecb0c1..de53ef7 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/factory/CollectionReaderFactory.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/factory/CollectionReaderFactory.java
@@ -43,6 +43,7 @@
 import org.apache.uima.resource.ExternalResourceDescription;
 import org.apache.uima.resource.ResourceCreationSpecifier;
 import org.apache.uima.resource.ResourceInitializationException;
+import org.apache.uima.resource.ResourceManager;
 import org.apache.uima.resource.ResourceSpecifier;
 import org.apache.uima.resource.metadata.Capability;
 import org.apache.uima.resource.metadata.ConfigurationParameter;
@@ -162,12 +163,12 @@
    */
   public static CollectionReader createReader(String descriptorName,
           Object... configurationData) throws UIMAException, IOException {
+    ResourceManager resMgr = ResourceManagerFactory.newResourceManager();
     Import imp = UIMAFramework.getResourceSpecifierFactory().createImport();
     imp.setName(descriptorName);
-    URL url = imp.findAbsoluteUrl(ResourceManagerFactory.newResourceManager());
+    URL url = imp.findAbsoluteUrl(resMgr);
     ResourceSpecifier specifier = createResourceCreationSpecifier(url, configurationData);
-    return UIMAFramework.produceCollectionReader(specifier,
-            ResourceManagerFactory.newResourceManager(), null);
+    return UIMAFramework.produceCollectionReader(specifier, resMgr, null);
   }
 
   /**
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/pipeline/JCasIterable.java b/uimafit-core/src/main/java/org/apache/uima/fit/pipeline/JCasIterable.java
index f9af785..d65eb99 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/pipeline/JCasIterable.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/pipeline/JCasIterable.java
@@ -63,9 +63,11 @@
     engines = aEngines;
   }
 
+  @Override
   public JCasIterator iterator() {
+    ResourceManager resMgr = null;
     try {
-      ResourceManager resMgr = ResourceManagerFactory.newResourceManager();
+      resMgr = ResourceManagerFactory.newResourceManager();
       
       // Create the components
       CollectionReader readerInst = UIMAFramework.produceCollectionReader(reader, resMgr, null);
@@ -76,12 +78,17 @@
       // Instantiate AAE
       AnalysisEngine aaeInst = UIMAFramework.produceAnalysisEngine(aaeDesc, resMgr, null);
       
-      JCasIterator i = new JCasIterator(readerInst, aaeInst);
+      JCasIterator i = new JCasIterator(resMgr, readerInst, aaeInst);
       i.setSelfComplete(true);
       i.setSelfDestroy(true);
       return i;
     } catch (UIMAException e) {
       throw new IllegalStateException(e);
     }
+    finally {
+      if (resMgr != null) {
+        resMgr.destroy();
+      }
+    }
   }
 }
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/pipeline/JCasIterator.java b/uimafit-core/src/main/java/org/apache/uima/fit/pipeline/JCasIterator.java
index b820847..79fb434 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/pipeline/JCasIterator.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/pipeline/JCasIterator.java
@@ -45,7 +45,6 @@
  * A class implementing iteration over a the documents of a collection. Each element in the Iterable
  * is a JCas containing a single document. The documents have been loaded by the CollectionReader
  * and processed by the AnalysisEngine (if any).
- * 
  */
 public class JCasIterator implements Iterator<JCas> {
 
@@ -60,6 +59,10 @@
   private boolean selfDestroy = false;
   
   private boolean destroyed = false;
+  
+  private ResourceManager resMgr;
+  
+  private boolean resourceManagerCreatedInternally = false;
 
   /**
    * Iterate over the documents loaded by the CollectionReader, running the AnalysisEngine on each
@@ -77,6 +80,29 @@
    */
   public JCasIterator(final CollectionReader aReader, final AnalysisEngine... aEngines)
           throws CASException, ResourceInitializationException {
+    this(ResourceManagerFactory.newResourceManager(), aReader, aEngines);
+    resourceManagerCreatedInternally = true;
+  }
+
+  /**
+   * Iterate over the documents loaded by the CollectionReader, running the AnalysisEngine on each
+   * one before yielding them. By default, components get no lifecycle events, such as
+   * collectionProcessComplete or destroy when this constructor is used.
+   * @param aResMgr 
+   *          The ResourceManager. Should be the one also used by the CollectionReader and 
+   *          AnalysisEngines.
+   * @param aReader
+   *          The CollectionReader for loading documents.
+   * @param aEngines
+   *          The AnalysisEngines for processing documents.
+   * @throws ResourceInitializationException
+   *           if a failure occurs during initialization of the components
+   * @throws CASException
+   *           if the JCas could not be initialized
+   */
+  public JCasIterator(final ResourceManager aResMgr, final CollectionReader aReader,
+          final AnalysisEngine... aEngines) throws CASException, ResourceInitializationException {
+    resMgr = aResMgr;
     collectionReader = aReader;
     analysisEngines = aEngines;
 
@@ -86,8 +112,7 @@
       metaData.add(ae.getProcessingResourceMetaData());
     }
 
-    ResourceManager resMgr = ResourceManagerFactory.newResourceManager();
-    jCas = CasCreationUtils.createCas(metaData, null, resMgr).getJCas();
+    jCas = CasCreationUtils.createCas(metaData, null, aResMgr).getJCas();
     collectionReader.typeSystemInit(jCas.getTypeSystem());
   }
 
@@ -111,6 +136,7 @@
     this(aReader, createEngine(NoOpAnnotator.class, aTypeSystemDescription));
   }
 
+  @Override
   public boolean hasNext() {
     if (destroyed) {
       return false;
@@ -132,6 +158,7 @@
     }
   }
 
+  @Override
   public JCas next() {
     jCas.reset();
     boolean error = true;
@@ -170,6 +197,7 @@
     return jCas;
   }
 
+  @Override
   public void remove() {
     throw new UnsupportedOperationException();
   }
@@ -192,6 +220,9 @@
       LifeCycleUtil.close(collectionReader);
       LifeCycleUtil.destroy(collectionReader);
       LifeCycleUtil.destroy(analysisEngines);
+      if (resourceManagerCreatedInternally) {
+        LifeCycleUtil.destroy(resMgr);
+      }
       destroyed = true;
     }
   }
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/pipeline/SimplePipeline.java b/uimafit-core/src/main/java/org/apache/uima/fit/pipeline/SimplePipeline.java
index 72365cc..25ab4ff 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/pipeline/SimplePipeline.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/pipeline/SimplePipeline.java
@@ -67,6 +67,9 @@
    * Note that with this method, external resources cannot be shared between the reader and the
    * analysis engines. They can be shared amongst the analysis engines.
    * </p>
+   * <p>
+   * The CAS is created using the resource manager used by the collection reader.
+   * </p>
    * 
    * @param reader
    *          The CollectionReader that loads the documents into the CAS.
@@ -90,9 +93,8 @@
       aae = createEngine(aaeDesc);
   
       // Create CAS from merged metadata
-      ResourceManager resMgr = ResourceManagerFactory.newResourceManager();
       final CAS cas = CasCreationUtils.createCas(asList(reader.getMetaData(), aae.getMetaData()), 
-              null, resMgr);
+              null, reader.getResourceManager());
       reader.typeSystemInit(cas.getTypeSystem());
 
       // Process
@@ -153,8 +155,9 @@
     
     CollectionReader reader = null;
     AnalysisEngine aae = null;
+    ResourceManager resMgr = null;
     try {
-      ResourceManager resMgr = ResourceManagerFactory.newResourceManager();
+      resMgr = ResourceManagerFactory.newResourceManager();
       
       // Create the components
       reader = UIMAFramework.produceCollectionReader(readerDesc, resMgr, null);
@@ -183,6 +186,7 @@
       // Destroy
       LifeCycleUtil.destroy(reader);
       LifeCycleUtil.destroy(aae);
+      LifeCycleUtil.destroy(resMgr);
     }
   }
 
@@ -199,6 +203,9 @@
    * External resources can only be shared between the reader and/or the analysis engines if the
    * reader/engines have been previously instantiated using a shared resource manager.
    * </p>
+   * <p>
+   * The CAS is created using the resource manager used by the collection reader.
+   * </p>
    * 
    * @param reader
    *          a collection reader
@@ -211,14 +218,44 @@
    */
   public static void runPipeline(final CollectionReader reader, final AnalysisEngine... engines)
           throws UIMAException, IOException {
+    runPipeline(reader.getResourceManager(), reader, engines);
+  }
+  
+  /**
+   * <p>
+   * Provides a simple way to run a pipeline for a given collection reader and sequence of analysis
+   * engines. After processing all CASes provided by the reader, the method calls
+   * {@link AnalysisEngine#collectionProcessComplete() collectionProcessComplete()} on the engines.
+   * Note that {@link AnalysisEngine#destroy()} and {@link CollectionReader#destroy()} are
+   * <b>NOT</b> called. As the components were instantiated by the caller, they must also be managed
+   * (i.e. destroyed) the caller.
+   * </p>
+   * <p>
+   * External resources can only be shared between the reader and/or the analysis engines if the
+   * reader/engines have been previously instantiated using a shared resource manager.
+   * </p>
+   * 
+   * @param aResMgr
+   *          a resource manager. Normally the same one used by the collection reader and analysis
+   *          engines.
+   * @param reader
+   *          a collection reader
+   * @param engines
+   *          a sequence of analysis engines
+   * @throws UIMAException
+   *           if there is a problem initializing or running the CPE.
+   * @throws IOException
+   *           if there is an I/O problem in the reader
+   */
+  public static void runPipeline(final ResourceManager aResMgr, final CollectionReader reader,
+          final AnalysisEngine... engines) throws UIMAException, IOException {
     final List<ResourceMetaData> metaData = new ArrayList<ResourceMetaData>();
     metaData.add(reader.getMetaData());
     for (AnalysisEngine engine : engines) {
       metaData.add(engine.getMetaData());
     }
 
-    ResourceManager resMgr = ResourceManagerFactory.newResourceManager();
-    final CAS cas = CasCreationUtils.createCas(metaData, null, resMgr);
+    final CAS cas = CasCreationUtils.createCas(metaData, null, aResMgr);
     reader.typeSystemInit(cas.getTypeSystem());
 
     while (reader.hasNext()) {
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/util/LifeCycleUtil.java b/uimafit-core/src/main/java/org/apache/uima/fit/util/LifeCycleUtil.java
index 799785e..5a10dc7 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/util/LifeCycleUtil.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/util/LifeCycleUtil.java
@@ -24,6 +24,7 @@
 import org.apache.uima.analysis_engine.AnalysisEngineProcessException;
 import org.apache.uima.collection.base_cpm.BaseCollectionReader;
 import org.apache.uima.resource.Resource;
+import org.apache.uima.resource.ResourceManager;
 
 /**
  * Helper methods to handle the life cycle of UIMA components.
@@ -51,6 +52,18 @@
   }
 
   /**
+   * Destroy a set of {@link ResourceManager resource manager}.
+   * 
+   * @param aResMgr
+   *          the resource manager to destroy
+   */
+  public static void destroy(final ResourceManager aResMgr) {
+    if (aResMgr != null) {
+      aResMgr.destroy();
+    }
+  }
+
+  /**
    * Destroy a set of {@link Resource resources}.
    * 
    * @param resources