Author: schor
Date: Tue Sep 14 19:10:21 2010
New Revision: 997044

URL: http://svn.apache.org/viewvc?rev=997044&view=rev
Log:
[UIMA-1722] Change CasCreationUtils part where to get metadata info, it 
instantiates (produceResource) a resource, gets the meta data info, and then 
destroys the resource - to cache these for 30 -60 seconds, to avoid repeatedly 
doing this for the same resource, since it can be very expensive (e.g., if the 
resource is a "custom Resource Specifier" used to denote a UIMA-AS remote 
service, this entails setting up a connection to that remote resource, and 
querying it, etc.). The short timeout insures that if a service becomes 
available, it won't be too long before the CDE would notice this ...

Change CDE to increase the odds of reusing things (like the class loader built 
for the particular project / datapath), and reducing the number of times it 
requests getting the metadata.

These two changes greatly speed up CDE opening when it has references to remote 
async services.

Modified:
    
uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/impl/ResourceManager_impl.java
    
uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/CasCreationUtils.java
    
uima/uimaj/trunk/uimaj-ep-configurator/src/main/java/org/apache/uima/taeconfigurator/editors/MultiPageEditor.java

Modified: 
uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/impl/ResourceManager_impl.java
URL: 
http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/impl/ResourceManager_impl.java?rev=997044&r1=997043&r2=997044&view=diff
==============================================================================
--- 
uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/impl/ResourceManager_impl.java
 (original)
+++ 
uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/impl/ResourceManager_impl.java
 Tue Sep 14 19:10:21 2010
@@ -119,6 +119,25 @@ public class ResourceManager_impl implem
     mRelativePathResolver = new RelativePathResolver_impl();
   }
 
+ /**
+  * Support reusing UIMA Class Loader instances to speed up
+  * things including the Component Description Editor when
+  * obtaining info from CustomResourceSpecifiers
+  * https://issues.apache.org/jira/browse/UIMA-1722
+  * @param uimaCL
+  * @param resolveResource
+  */
+ public void setExtensionClassPath(UIMAClassLoader uimaCL, boolean 
resolveResource) {
+   this.uimaCL = uimaCL;
+   
+   if (resolveResource) {
+     // set UIMA extension ClassLoader also to resolve resources
+     getRelativePathResolver().setPathResolverClassLoader(uimaCL);
+   }
+ }
+
+ /**
+
   /**
    * @see 
org.apache.uima.resource.ResourceManager#setExtensionClassPath(java.lang.String,
 boolean)
    */

Modified: 
uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/CasCreationUtils.java
URL: 
http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/CasCreationUtils.java?rev=997044&r1=997043&r2=997044&view=diff
==============================================================================
--- 
uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/CasCreationUtils.java
 (original)
+++ 
uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/CasCreationUtils.java
 Tue Sep 14 19:10:21 2010
@@ -22,7 +22,6 @@ package org.apache.uima.util;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -30,8 +29,11 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.Map.Entry;
 
 import org.apache.uima.UIMAFramework;
 import org.apache.uima.analysis_engine.AnalysisEngineDescription;
@@ -1673,6 +1675,129 @@ public class CasCreationUtils {
     ;
   }
 
+  
/*************************************************************************************************
+   * Caching of getMeta info that requires producing the resource              
                    *
+   *   - done because producing the resource can be very expensive             
                    *                        
+   *     including accessing remote things on the network                      
                    *
+   * Cache is cleared approximately every 30 seconds because remote resource's 
statuses may change *
+   *                                                                           
                    *
+   * Cache key is the ResourceSpecifier's class loaders and the 
ResourceManager                    *
+   *   Both the DataPath and the uima extension class loader are used as part 
of the key           *
+   *   because differences in these could cause different metadata to be 
loaded                    *
+   
*************************************************************************************************/
+  
+  private static class MetaDataCacheKey {
+    final ResourceSpecifier resourceSpecifier;
+    final ClassLoader rmClassLoader;
+    final String rmDataPath;
+    
+    MetaDataCacheKey(ResourceSpecifier resourceSpecifier, ResourceManager 
resourceManager) {
+      this.resourceSpecifier = resourceSpecifier;
+      this.rmClassLoader = (null == resourceManager) ? null : 
resourceManager.getExtensionClassLoader(); // can be null
+      this.rmDataPath = (null == resourceManager) ? null : 
resourceManager.getDataPath();
+    }
+
+    @Override
+    public int hashCode() {
+      return ((rmClassLoader == null) ? 0 : rmClassLoader.hashCode()) 
+             + ((rmDataPath == null)  ? 0 : rmDataPath.hashCode()) 
+             + resourceSpecifier.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      MetaDataCacheKey k = (MetaDataCacheKey) obj;
+      if (rmDataPath == null) {
+        if (k.rmDataPath != null) {
+          return false;
+        }
+        return resourceSpecifier.equals(k.resourceSpecifier) && 
+               rmClassLoader == k.rmClassLoader;
+      }
+      return resourceSpecifier.equals(k.resourceSpecifier) && 
+             rmClassLoader == k.rmClassLoader &&
+             rmDataPath.equals(k.rmDataPath);
+    }
+
+    @Override
+    public String toString() {
+      return "MetaDataCacheKey [resourceSpecifier=" + resourceSpecifier + ", 
rmClassLoader="
+          + rmClassLoader + ", rmDataPath=" + rmDataPath + "]";
+    }
+  }
+  
+  private static final boolean cacheDebug = false; // set true for debugging 
info
+  private static final int HOLD_TIME = 30000;  // keep cache for 30 seconds, 
approx., in case a remote resource changes state
+  
+  /**
+   * This is the cache.
+   * All references to it are synchronized, using it as the object.
+   */
+  private static final transient Map<MetaDataCacheKey, MetaDataCacheEntry> 
metaDataCache = new HashMap<MetaDataCacheKey, MetaDataCacheEntry>();
+
+  /** This holds an instance of a Timer object
+   * This object is nulled out and gets gc'd when it's timertask finishes, 
when the
+   * cache is empty.  
+   * 
+   * All references to it are synchronized under the metaDataCache.
+   */
+  private static Timer cleanupTimer = null;
+
+  /**
+   * This class holds the processing Resource Metadata, or null if there is 
none, and
+   * a timestamp when the metadata was obtained.
+   */
+  private static class MetaDataCacheEntry {
+    ProcessingResourceMetaData processingResourceMetaData;
+    long creationTime;
+    
+    MetaDataCacheEntry(ResourceMetaData resourceMetaData) {
+      processingResourceMetaData = (resourceMetaData instanceof 
ProcessingResourceMetaData) ? (ProcessingResourceMetaData) resourceMetaData : 
null;
+      creationTime = System.currentTimeMillis(); 
+      if (null == cleanupTimer) {
+        if (cacheDebug) {
+          System.err.format("GetMetaDataCache: Scheduling new cleanup task%n");
+        }
+
+        cleanupTimer = new Timer("metaDataCacheCleanup", true);  // run as 
daemon
+        // create a new instance of the timer task, because a previous one may 
+        // still be running
+        TimerTask metaDataCacheCleanupTask = new TimerTask() {   
+          @Override
+          public void run() {
+            synchronized (metaDataCache) {
+              long now = System.currentTimeMillis();
+              if (cacheDebug) {
+                System.err.format("GetMetaDataCache: cleanup task running%n");
+              }
+              for (Iterator<Entry<MetaDataCacheKey, MetaDataCacheEntry>> it = 
metaDataCache.entrySet().iterator(); it.hasNext();) {
+                Entry<MetaDataCacheKey, MetaDataCacheEntry> e = it.next();
+                if (e.getValue().creationTime + HOLD_TIME < now) {
+                  if (cacheDebug) {
+                    System.err.format("GetMetaDataCache: cleanup task removing 
entry %s%n", e.getKey().toString() );
+                  }
+                  it.remove();
+                }
+              }
+              if (metaDataCache.size() == 0) {
+                if (cacheDebug) {
+                  System.err.format("GetMetaDataCache: cleanup task 
terminating, cache empty%n");
+                }
+                cancel();
+                cleanupTimer = null;
+              }
+              if (cacheDebug) {
+                System.err.format("GetMetaDataCache: cleanup task finished a 
cycle%n");
+              }
+            }
+          }
+        };
+        cleanupTimer.scheduleAtFixedRate(metaDataCacheCleanupTask, HOLD_TIME, 
HOLD_TIME);
+      }
+    }
+  }
+   
+  
   /**
    * Gets a list of ProcessingResourceMetadata objects from a list containing 
either
    * ResourceSpecifiers, ProcessingResourceMetadata objects, or subparts of
@@ -1690,6 +1815,13 @@ public class CasCreationUtils {
    * service will be queries for its metadata. An exception will be thrown if 
the connection can not
    * be opened.
    * 
+   * Note that this last kind of lookup may be expensive (calling 
produceResource, which in turn may
+   * query remote connections etc.).  Because of this, a cache is maintained 
for these, 
+   * (because some scenarios end up requesting the same metadata multiple 
times, in rapid succession).
+   * 
+   * Because remote resource may become available, the cache entries are 
removed 30 seconds
+   * after they are created.  This also reclaims space from the cache.
+   *  
    * @param aComponentDescriptionOrMetaData
    *                a collection of {...@link ResourceSpecifier}, {...@link 
ProcessingResourceMetaData},
    *                {...@link TypeSystemDescription}, {...@link 
FsIndexCollection}, or
@@ -1770,7 +1902,24 @@ public class CasCreationUtils {
         md.setTypePriorities((TypePriorities) current);
         mdList.add(md);
       } else if (current instanceof ResourceSpecifier) {
+                
+        // first try the cache
+        MetaDataCacheKey metaDataCacheKey = new 
MetaDataCacheKey((ResourceSpecifier)current, aResourceManager);
+        synchronized(metaDataCache) {
+          MetaDataCacheEntry metaData = metaDataCache.get(metaDataCacheKey);
+          if (null != metaData) {
+            if (cacheDebug) {
+              System.err.format("GetMetaDataCache: using cached entry%n");
+            }
+            if (null != metaData.processingResourceMetaData) {
+              mdList.add(metaData.processingResourceMetaData);
+            }
+            continue;
+          } 
+        }
+        
         // try to instantiate the resource
+        
         Resource resource = null;
         Map<String, Object> prParams = new HashMap<String, Object>();
         if (aResourceManager != null) {
@@ -1781,6 +1930,13 @@ public class CasCreationUtils {
           resource = UIMAFramework.produceResource((ResourceSpecifier) 
current, prParams);
 //              (null == aResourceManager) ? Collections.<String, 
Object>emptyMap() : resourceMgrInMap);
         } catch (Exception e) {
+          // record failure, so we don't ask for this again, for a while
+          synchronized (metaDataCache) {
+            if (cacheDebug) {
+              System.err.format("GetMetaDataCache: saving entry in cache%n");
+            }
+            metaDataCache.put(metaDataCacheKey, new MetaDataCacheEntry(null));
+          }
           // failed. If aOutputFailedRemotes is non-null, add an entry to it 
to it, else throw the
           // exception.
           if (aOutputFailedRemotes != null) {
@@ -1794,8 +1950,16 @@ public class CasCreationUtils {
               throw new RuntimeException(e);
           }
         }
+        ResourceMetaData metadata = (resource == null) ? null : 
resource.getMetaData();
+
+        synchronized (metaDataCache) {
+          if (cacheDebug) {
+            System.err.format("GetMetaDataCache: saving entry in cache%n");
+          }
+          metaDataCache.put(metaDataCacheKey, new 
MetaDataCacheEntry(metadata));
+        }
+
         if (resource != null) {
-          ResourceMetaData metadata = resource.getMetaData();
           if (metadata instanceof ProcessingResourceMetaData) {
             mdList.add((ProcessingResourceMetaData) metadata);
           }

Modified: 
uima/uimaj/trunk/uimaj-ep-configurator/src/main/java/org/apache/uima/taeconfigurator/editors/MultiPageEditor.java
URL: 
http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-ep-configurator/src/main/java/org/apache/uima/taeconfigurator/editors/MultiPageEditor.java?rev=997044&r1=997043&r2=997044&view=diff
==============================================================================
--- 
uima/uimaj/trunk/uimaj-ep-configurator/src/main/java/org/apache/uima/taeconfigurator/editors/MultiPageEditor.java
 (original)
+++ 
uima/uimaj/trunk/uimaj-ep-configurator/src/main/java/org/apache/uima/taeconfigurator/editors/MultiPageEditor.java
 Tue Sep 14 19:10:21 2010
@@ -29,6 +29,7 @@ import java.io.InputStream;
 import java.io.PrintStream;
 import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
+import java.lang.ref.SoftReference;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
@@ -58,12 +59,14 @@ import org.apache.uima.collection.CasCon
 import org.apache.uima.collection.CasInitializerDescription;
 import org.apache.uima.collection.CollectionReaderDescription;
 import org.apache.uima.flow.FlowControllerDescription;
+import org.apache.uima.internal.util.UIMAClassLoader;
 import org.apache.uima.jcas.jcasgenp.MergerImpl;
 import org.apache.uima.resource.ResourceCreationSpecifier;
 import org.apache.uima.resource.ResourceInitializationException;
 import org.apache.uima.resource.ResourceManager;
 import org.apache.uima.resource.ResourceServiceSpecifier;
 import org.apache.uima.resource.ResourceSpecifier;
+import org.apache.uima.resource.impl.ResourceManager_impl;
 import org.apache.uima.resource.metadata.Capability;
 import org.apache.uima.resource.metadata.FsIndexCollection;
 import org.apache.uima.resource.metadata.Import;
@@ -1087,18 +1090,41 @@ public class MultiPageEditor extends For
     return rm;
   }
 
+  private String cachedRMclassPath = null; 
+  private SoftReference<UIMAClassLoader> cachedRMcl = new 
SoftReference<UIMAClassLoader>(null);
+
   public ResourceManager createResourceManager(String classPath) {
-    // workspacePath = 
TAEConfiguratorPlugin.getWorkspace().getRoot().getLocation().toString();
     ResourceManager resourceManager = 
UIMAFramework.newDefaultResourceManager();
 
     try {
-      if (null == classPath)
+      if (null == classPath) {
         classPath = getProjectClassPath();
-      // first arg in next is the parent of the class loader.  Make it be the
-      //   uima framework's class loader (not this class's class loader)
-      //   so the validation tests work properly (that test isAssignableFrom)
-      
resourceManager.setExtensionClassPath(UIMAFramework.class.getClassLoader(), 
classPath, true);
-      resourceManager.setDataPath(CDEpropertyPage.getDataPath(getProject()));
+      }      
+      String dataPath = CDEpropertyPage.getDataPath(getProject());
+      
+      // first try to get the value of the class loader from the last (cached)
+      // value - should succeed frequently because the class loader is only 
dependent
+      // on the value of the classpath
+      
+      UIMAClassLoader uimaCL = null;
+      if (cachedRMclassPath != null &&
+          cachedRMclassPath.equals(classPath)) {
+        uimaCL = cachedRMcl.get();
+      }
+      
+      if (uimaCL != null) {
+        ((ResourceManager_impl)resourceManager).setExtensionClassPath(uimaCL, 
true);
+      } else {
+        // first arg in next is the parent of the class loader.  Make it be the
+        //   uima framework's class loader (not this class's class loader)
+        //   so the validation tests work properly (that test isAssignableFrom)
+        
resourceManager.setExtensionClassPath(UIMAFramework.class.getClassLoader(), 
classPath, true);
+        cachedRMclassPath = classPath;
+        cachedRMcl = new SoftReference<UIMAClassLoader>((UIMAClassLoader) 
resourceManager.getExtensionClassLoader());
+      }
+      
+      // in any case, set the data path
+      resourceManager.setDataPath(dataPath);
     } catch (MalformedURLException e1) {
       throw new InternalErrorCDE(Messages.getString("MultiPageEditor.14"), 
e1); //$NON-NLS-1$
     } catch (CoreException e1) {
@@ -1437,19 +1463,20 @@ public class MultiPageEditor extends For
     } catch (InvalidXMLException e) {
       throw new ResourceInitializationException(e);
     }
+    // get the metadata once, because it can be expensive to do     
+    AnalysisEngineMetaData md = aeDescription.getAnalysisEngineMetaData();
+
     // These come before setTypeSystemDescription call because that call
-    // invokeds tcas validate, which uses the merged values for speedup
+    // invokes tcas validate, which uses the merged values for speedup
     // Here we set them to values that won't cause errors. They're set to 
actual values below.
-    mergedFsIndexCollection = 
aeDescription.getAnalysisEngineMetaData().getFsIndexCollection();
-    mergedTypePriorities = 
aeDescription.getAnalysisEngineMetaData().getTypePriorities();
+    mergedFsIndexCollection = md.getFsIndexCollection();
+    mergedTypePriorities = md.getTypePriorities();
     resolvedExternalResourcesAndBindings = 
aeDescription.getResourceManagerConfiguration();
     resolvedFlowControllerDeclaration = 
aeDescription.getFlowControllerDeclaration();
 
-    setTypeSystemDescription(aeDescription.isPrimitive() ? aeDescription
-            .getAnalysisEngineMetaData().getTypeSystem() : null); // 
aggregates have null
-    // tsd. If passed in one
-    // isn't null, make it
-    // null.
+    setTypeSystemDescription(aeDescription.isPrimitive() ? md.getTypeSystem() 
: null); 
+    // aggregates have null type system descriptors. 
+    // If passed in one that isn't null, make it null.
 
     // These come after setTypeSystemDescription call, even though
     // that call invokeds tcas validate, which uses the merged values for 
speedup
@@ -2957,7 +2984,7 @@ public class MultiPageEditor extends For
       sb.append("Component key-name(s): ").append(names.get(i))
         .append(": ")
         .append(getMessagesToRootCause((Exception)exceptions.get(i)))
-        .append("\n");
+        .append("\n---------------\n");
     }
     
     Utility.popMessage("Remotes Unavailable", "Note: This message is only 
shown once.\n\n" +


Reply via email to