You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@uima.apache.org by re...@apache.org on 2020/11/02 13:39:44 UTC

[uima-uimaj] 01/01: [UIMA-6287] FSArray.spliterator() fails to handle PEAR case

This is an automated email from the ASF dual-hosted git repository.

rec pushed a commit to branch bugfix/UIMA-6287-FSArray.spliterator-fails-to-handle-PEAR-case
in repository https://gitbox.apache.org/repos/asf/uima-uimaj.git

commit c181642a479a71ef91180fd77fb8008961c269e2
Author: Richard Eckart de Castilho <re...@apache.org>
AuthorDate: Mon Nov 2 14:39:34 2020 +0100

    [UIMA-6287] FSArray.spliterator() fails to handle PEAR case
    
    - Added unit test for the issue that the spliterator is returning the wrong type
    - Refactored other JCas classloading tests a bit in the process
---
 .../apache/uima/cas/test/JCasClassLoaderTest.java  | 240 +++++++++++++++++----
 1 file changed, 201 insertions(+), 39 deletions(-)

diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/test/JCasClassLoaderTest.java b/uimaj-core/src/test/java/org/apache/uima/cas/test/JCasClassLoaderTest.java
index b192e44..ef21e5d 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/test/JCasClassLoaderTest.java
+++ b/uimaj-core/src/test/java/org/apache/uima/cas/test/JCasClassLoaderTest.java
@@ -24,7 +24,10 @@ import static org.apache.uima.UIMAFramework.getResourceSpecifierFactory;
 import static org.apache.uima.UIMAFramework.getXMLParser;
 import static org.apache.uima.UIMAFramework.newDefaultResourceManager;
 import static org.apache.uima.UIMAFramework.produceAnalysisEngine;
+import static org.apache.uima.cas.CAS.TYPE_NAME_ANNOTATION;
+import static org.apache.uima.cas.CAS.TYPE_NAME_FS_ARRAY;
 import static org.apache.uima.util.CasCreationUtils.createCas;
+import static org.apache.uima.util.CasCreationUtils.mergeTypeSystems;
 
 import java.io.File;
 import java.io.IOException;
@@ -41,6 +44,7 @@ import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
@@ -51,12 +55,15 @@ import org.apache.uima.analysis_engine.AnalysisEngineDescription;
 import org.apache.uima.analysis_engine.AnalysisEngineProcessException;
 import org.apache.uima.analysis_engine.impl.PrimitiveAnalysisEngine_impl;
 import org.apache.uima.cas.CAS;
-import org.apache.uima.cas.CASException;
+import org.apache.uima.cas.FeatureStructure;
 import org.apache.uima.cas.impl.CASImpl;
+import org.apache.uima.cas.text.AnnotationFS;
 import org.apache.uima.jcas.JCas;
+import org.apache.uima.jcas.cas.FSArray;
 import org.apache.uima.jcas.tcas.Annotation;
 import org.apache.uima.resource.ResourceInitializationException;
 import org.apache.uima.resource.ResourceManager;
+import org.apache.uima.resource.metadata.TypeDescription;
 import org.apache.uima.resource.metadata.TypeSystemDescription;
 import org.apache.uima.util.InvalidXMLException;
 import org.apache.uima.util.XMLInputSource;
@@ -66,25 +73,38 @@ import org.junit.Test;
 
 public class JCasClassLoaderTest {
   
-  public static Object casTokenClass;
-  public static Object addTokenAETokenClass;
-  public static Object fetchTokenAETokenClass;
-  public static Object indexedTokenClass;
+  public static final String TYPE_NAME_TOKEN = Token.class.getName();
+  public static final String TYPE_NAME_ARRAY_HOST = "uima.testing.ArrayHost";
+  public static final String FEAT_NAME_ARRAY_HOST_VALUES = "values";
+    
+  public static Class casTokenClassViaClassloader;
+  public static Class casTokenClassViaCas;
+  public static Class addTokenAETokenClass;
+  public static Class fetchTokenAETokenClass;
+  public static Class indexedTokenClass;
   public static boolean fetchThrowsClassCastException;
-  
+
+  public static Class tokenClassAddedToArray;
+  public static Class tokenClassFetchedFromArray;
+
   @Before
   public void setup()
   {
-    casTokenClass = null;
+    casTokenClassViaClassloader = null;
+    casTokenClassViaCas = null;
     addTokenAETokenClass = null;
     fetchTokenAETokenClass = null;
+    indexedTokenClass = null;
     fetchThrowsClassCastException = false;
+    
+    tokenClassAddedToArray = null;
+    tokenClassFetchedFromArray = null;
   }
   
   /**
    * This test simulates an environment as it could exist when using e.g. PEARs. We use a vanilla
-   * JCas and the analysis engines each use local JCas wrappers which are provided as an extra classpath
-   * passed to the resource manager.
+   * JCas and the analysis engines each use local JCas wrappers which are provided as an extra
+   * classpath passed to the resource manager.
    * 
    * <ul>
    * <li>JCas does not know JCas wrappers for the {@code Token} type.</li>
@@ -105,9 +125,8 @@ public class JCasClassLoaderTest {
     ClassLoader rootCl = getClass().getClassLoader();
 
     // We do not want the CAS to know the Token JCas wrapper when it gets initialized
-    ClassLoader clForCas = new IsolatingClassloader("CAS", rootCl)
+    IsolatingClassloader clForCas = new IsolatingClassloader("CAS", rootCl)
             .hiding("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
-
     
     File cpBase = new File("target/test-output/JCasClassLoaderTest/classes");
     File cpPackageBase = new File(cpBase, "org/apache/uima/cas/test");
@@ -134,7 +153,8 @@ public class JCasClassLoaderTest {
     fetchTheTokenAnnotator.process(jcas);
     
     try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) {
-      softly.assertThat(casTokenClass).isNull();
+      softly.assertThat(casTokenClassViaClassloader).isNull();
+      softly.assertThat(casTokenClassViaCas).isSameAs(Annotation.class);
       softly.assertThat(addTokenAETokenClass).isNotNull();
       softly.assertThat(fetchTokenAETokenClass).isNotNull();
       softly.assertThat(addTokenAETokenClass)
@@ -173,7 +193,7 @@ public class JCasClassLoaderTest {
     ClassLoader rootCl = getClass().getClassLoader();
 
     // We do not want the CAS to know the Token JCas wrapper when it gets initialized
-    ClassLoader clForCas = new IsolatingClassloader("CAS", rootCl)
+    IsolatingClassloader clForCas = new IsolatingClassloader("CAS", rootCl)
             .hiding("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
     ClassLoader clForAddATokenAnnotator = new IsolatingClassloader("AddATokenAnnotator", rootCl)
@@ -197,7 +217,8 @@ public class JCasClassLoaderTest {
     fetchTheTokenAnnotator.process(jcas);
     
     try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) {
-      softly.assertThat(casTokenClass).isNull();
+      softly.assertThat(casTokenClassViaClassloader).isNull();
+      softly.assertThat(casTokenClassViaCas).isSameAs(Annotation.class);
       softly.assertThat(addTokenAETokenClass).isNotNull();
       softly.assertThat(fetchTokenAETokenClass).isNotNull();
       softly.assertThat(addTokenAETokenClass)
@@ -236,7 +257,7 @@ public class JCasClassLoaderTest {
   public void thatAnnotatorsCanLocallyUseDifferentJCasWrappers() throws Exception {
     ClassLoader rootCl = getClass().getClassLoader();
 
-    ClassLoader clForCas = new IsolatingClassloader("CAS", rootCl);
+    IsolatingClassloader clForCas = new IsolatingClassloader("CAS", rootCl);
 
     ClassLoader clForAddATokenAnnotator = new IsolatingClassloader("AddATokenAnnotator", rootCl)
             .redefining("^.*AddATokenAnnotator$")
@@ -259,13 +280,14 @@ public class JCasClassLoaderTest {
     fetchTheTokenAnnotator.process(jcas);
 
     try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) {
-      softly.assertThat(casTokenClass).isNotNull();
+      softly.assertThat(casTokenClassViaClassloader).isNotNull();
+      softly.assertThat(casTokenClassViaCas).isSameAs(Token.class);
       softly.assertThat(addTokenAETokenClass).isNotNull();
       softly.assertThat(fetchTokenAETokenClass).isNotNull();
-      softly.assertThat(casTokenClass)
+      softly.assertThat(casTokenClassViaClassloader)
           .as("JCas and AddTokenAnnotator use different Token wrappers")
           .isNotEqualTo(addTokenAETokenClass);
-      softly.assertThat(casTokenClass)
+      softly.assertThat(casTokenClassViaClassloader)
           .as("JCas and FetchTokenAnnotator use different Token wrappers")
           .isNotEqualTo(fetchTokenAETokenClass);
       softly.assertThat(addTokenAETokenClass)
@@ -316,7 +338,7 @@ public class JCasClassLoaderTest {
   public void thatTypeSystemCanComeFromItsOwnClassLoader() throws Exception {
     ClassLoader rootCl = getClass().getClassLoader();
 
-    ClassLoader clForCas = new IsolatingClassloader("CAS", rootCl)
+    IsolatingClassloader clForCas = new IsolatingClassloader("CAS", rootCl)
             .hiding("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
     ClassLoader clForTS = new IsolatingClassloader("TS", rootCl)
@@ -343,13 +365,14 @@ public class JCasClassLoaderTest {
     fetchTheTokenAnnotator.process(jcas);
     
     try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) {
-      softly.assertThat(casTokenClass).isNull();
+      softly.assertThat(casTokenClassViaClassloader).isNull();
+      softly.assertThat(casTokenClassViaCas).isSameAs(Annotation.class);
       softly.assertThat(addTokenAETokenClass).isNotNull();
       softly.assertThat(fetchTokenAETokenClass).isNotNull();
-      softly.assertThat(casTokenClass)
+      softly.assertThat(casTokenClassViaClassloader)
           .as("JCas and AddTokenAnnotator use different Token wrappers")
           .isNotEqualTo(addTokenAETokenClass);
-      softly.assertThat(casTokenClass)
+      softly.assertThat(casTokenClassViaClassloader)
           .as("JCas and FetchTokenAnnotator use different Token wrappers")
           .isNotEqualTo(fetchTokenAETokenClass);
       softly.assertThat(addTokenAETokenClass)
@@ -363,18 +386,64 @@ public class JCasClassLoaderTest {
           .isFalse();
     }
   }
+  
+  @Test
+  public void thatFSArraySpliteratorReturnsProperJCasWrapper() throws Exception {
+      ClassLoader rootCl = getClass().getClassLoader();
+
+      IsolatingClassloader clForCas = new IsolatingClassloader("CAS", rootCl)
+              .hiding("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
+
+      ClassLoader clForCreators = new IsolatingClassloader("Creators", rootCl)
+              .hiding("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*")
+              .redefining("^.*AddATokenAnnotatorNoJCas$")
+              .redefining("^.*AddTokenToArrayAnnotatorNoJCas$");
+
+      ClassLoader clForAccessors = new IsolatingClassloader("Accessors", rootCl)
+              .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*")
+              .redefining("^.*FetchTokenFromArrayViaSpliteratorAnnotator$");
+
+      TypeSystemDescription tsd = mergeTypeSystems(
+              asList(loadTokensAndSentencesTS(), makeArrayTestTS()));
+      
+      JCas jcas = makeJCas(clForCas, tsd);
+      AnalysisEngine addATokenAnnotator = makeAnalysisEngine(AddATokenAnnotatorNoJCas.class,
+              clForCreators);
+      AnalysisEngine addTokenToArrayAnnotator = makeAnalysisEngine(AddTokenToArrayAnnotatorNoJCas.class,
+              clForCreators);
+      AnalysisEngine fetchTokenFromArrayViaSpliteratorAnnotator = makeAnalysisEngine(
+              FetchTokenFromArrayViaSpliteratorAnnotator.class, clForAccessors);
+      
+      jcas.setDocumentText("test");
+      
+      addATokenAnnotator.process(jcas);
+      addTokenToArrayAnnotator.process(jcas);
+      fetchTokenFromArrayViaSpliteratorAnnotator.process(jcas);
+      
+      try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) {
+        softly.assertThat(casTokenClassViaClassloader).isNull();
+        softly.assertThat(casTokenClassViaCas).isSameAs(Annotation.class);
+        softly.assertThat(addTokenAETokenClass).isNotNull();
+        softly.assertThat(casTokenClassViaClassloader)
+            .as("JCas and AddTokenAnnotator use different Token wrappers")
+            .isNotEqualTo(addTokenAETokenClass);
+        softly.assertThat(tokenClassFetchedFromArray.getName())
+            .as("Array spliterator returns proper Token wrapper")
+            .isEqualTo(TYPE_NAME_TOKEN);
+      }      
+    }
 
-  private static Class<?> loadTokenClass(ClassLoader cl)
+  public static Class<?> loadTokenClass(ClassLoader cl)
   {
     try {
-      return cl.loadClass(Token.class.getName());
+      return cl.loadClass(TYPE_NAME_TOKEN);
     } catch (ClassNotFoundException e) {
       return null;
     }
   }
   
   
-  private static void printTokenClassLoaderInfo(String context, ClassLoader cl) {
+  public static void printTokenClassLoaderInfo(String context, ClassLoader cl) {
     Class<?> clazz = loadTokenClass(cl);
     if (clazz != null) {
       System.out.printf("[%s] %s %d %n", context, clazz.getName(), clazz.hashCode());
@@ -382,21 +451,36 @@ public class JCasClassLoaderTest {
       System.out.printf("[%s] %s NOT AVAILABLE %n", context, Token.class.getName());
     }
   }
+
+  private TypeSystemDescription makeArrayTestTS() throws InvalidXMLException, IOException
+  {
+      TypeSystemDescription tsd = getResourceSpecifierFactory().createTypeSystemDescription();
+      TypeDescription arrayHost = tsd.addType(TYPE_NAME_ARRAY_HOST, "", TYPE_NAME_ANNOTATION);
+      arrayHost.addFeature(FEAT_NAME_ARRAY_HOST_VALUES, "", TYPE_NAME_FS_ARRAY,
+              TYPE_NAME_ANNOTATION, true);
+      return tsd;
+  }
+
+  private TypeSystemDescription loadTokensAndSentencesTS() throws InvalidXMLException, IOException
+  {
+      return getXMLParser().parseTypeSystemDescription(new XMLInputSource(
+              new File("src/test/resources/CASTests/desc/TokensAndSentencesTS.xml")));
+  }
   
   /**
    * Creates a new JCas and sets it up so it uses the given classloader to load its JCas wrappers.
    */
-  private JCas makeJCas(ClassLoader cl)
-          throws ResourceInitializationException, CASException, InvalidXMLException, IOException {
-    printTokenClassLoaderInfo("JCas creation", cl);
-    casTokenClass = loadTokenClass(cl);
-    TypeSystemDescription tsd = getXMLParser().parseTypeSystemDescription(new XMLInputSource(
-            new File("src/test/resources/CASTests/desc/TokensAndSentencesTS.xml")));
-    ResourceManager resMgr = newDefaultResourceManager();
-    // resMgr.setExtensionClassPath(cl, "", false);
-    CASImpl cas = (CASImpl) createCas(tsd, null, null, null, resMgr);
-    cas.setJCasClassLoader(cl);
-    return cas.getJCas();
+  private JCas makeJCas(IsolatingClassloader cl)
+          throws Exception {
+    return makeJCas(cl, loadTokensAndSentencesTS());
+  }
+  
+  private JCas makeJCas(IsolatingClassloader cl, TypeSystemDescription tsd)
+          throws Exception {
+    cl.redefining("^.*JCasCreatorImpl$");
+    Class jcasCreatorClass = cl.loadClass(JCasCreatorImpl.class.getName());
+    JCasCreator creator = (JCasCreator) jcasCreatorClass.newInstance();
+    return creator.createJCas(cl, tsd);
   }
 
   /**
@@ -406,7 +490,7 @@ public class JCasClassLoaderTest {
   private AnalysisEngine makeAnalysisEngine(Class<? extends Annotator_ImplBase> aeClass,
           ClassLoader cl) throws ResourceInitializationException, MalformedURLException {
     ResourceManager resMgr = newDefaultResourceManager();
-    resMgr.setExtensionClassPath(cl, "", false);
+    resMgr.setExtensionClassLoader(cl, false);
     printTokenClassLoaderInfo("AE creation: " + aeClass.getSimpleName(), resMgr.getExtensionClassLoader());
     AnalysisEngineDescription desc = getResourceSpecifierFactory()
             .createAnalysisEngineDescription();
@@ -437,12 +521,89 @@ public class JCasClassLoaderTest {
     public void process(JCas aJCas) throws AnalysisEngineProcessException {
       addTokenAETokenClass = Token.class;
       System.out.printf("%s class loader: %s%n", getClass().getName(), getClass().getClassLoader());
-      System.out.printf("[AE runtime: %s] %s %d %n", getClass().getName(), Token.class.getName(),
-              Token.class.hashCode());
+      System.out.printf("[AE runtime: %s] %s %d %n", getClass().getName(),
+              addTokenAETokenClass.getName(), addTokenAETokenClass.hashCode());
       
       new Token(aJCas, 0, aJCas.getDocumentText().length()).addToIndexes();
     }
   }
+  
+  public static interface JCasCreator {
+    JCas createJCas(ClassLoader cl, TypeSystemDescription tsd);
+  }
+  
+  public static class JCasCreatorImpl implements JCasCreator {
+
+    @Override
+    public JCas createJCas(ClassLoader cl, TypeSystemDescription tsd)
+    {
+      try {
+        printTokenClassLoaderInfo("JCas creation", cl);
+        casTokenClassViaClassloader = loadTokenClass(cl);
+        ResourceManager resMgr = newDefaultResourceManager();
+        resMgr.setExtensionClassLoader(cl, false);
+        CASImpl cas = (CASImpl) createCas(tsd, null, null, null, resMgr);
+        cas.setJCasClassLoader(cl);
+
+        casTokenClassViaCas = cas
+                .createAnnotation(cas.getTypeSystem().getType(TYPE_NAME_TOKEN), 0, 0).getClass();
+        
+        return cas.getJCas();
+      }
+      catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+
+  public static class AddATokenAnnotatorNoJCas extends JCasAnnotator_ImplBase {
+    @Override
+    public void process(JCas aJCas) throws AnalysisEngineProcessException {
+      AnnotationFS token = aJCas.getCas().createAnnotation(
+              aJCas.getTypeSystem().getType(TYPE_NAME_TOKEN), 0,
+              aJCas.getDocumentText().length());
+      addTokenAETokenClass = token.getClass();
+      System.out.printf("[AE runtime: %s] CAS class loader: %s%n", getClass().getName(), aJCas.getCasImpl().getJCasClassLoader());
+      System.out.printf("[AE runtime: %s] AE class loader: %s%n", getClass().getName(), getClass().getClassLoader());
+      System.out.printf("[AE runtime: %s] %s %d loaded by %s%n", getClass().getName(), addTokenAETokenClass.getName(),
+              addTokenAETokenClass.hashCode(), addTokenAETokenClass.getClassLoader());
+      aJCas.getCas().addFsToIndexes(token);
+    }
+  }
+
+  public static class AddTokenToArrayAnnotatorNoJCas extends JCasAnnotator_ImplBase {
+    @Override
+    public void process(JCas aJCas) throws AnalysisEngineProcessException {
+
+      AnnotationFS token = (AnnotationFS) aJCas
+              .select(aJCas.getTypeSystem().getType(TYPE_NAME_TOKEN))
+              .single();
+      tokenClassAddedToArray = token.getClass();
+      
+      AnnotationFS arrayHost = aJCas.getCas()
+              .createAnnotation(aJCas.getTypeSystem().getType(TYPE_NAME_ARRAY_HOST), 0, 0);
+      FSArray array = new FSArray<>(aJCas, 1);
+      array.set(0, token);
+      arrayHost.setFeatureValue(
+              arrayHost.getType().getFeatureByBaseName(FEAT_NAME_ARRAY_HOST_VALUES), array);
+      aJCas.getCas().addFsToIndexes(arrayHost);
+    }
+  }
+
+  public static class FetchTokenFromArrayViaSpliteratorAnnotator extends JCasAnnotator_ImplBase {
+    @Override
+    public void process(JCas aJCas) throws AnalysisEngineProcessException {
+      FeatureStructure arrayHost = aJCas.select(aJCas.getTypeSystem().getType(TYPE_NAME_ARRAY_HOST))
+              .single();
+      
+      FSArray array = (FSArray) arrayHost.getFeatureValue(
+              arrayHost.getType().getFeatureByBaseName(FEAT_NAME_ARRAY_HOST_VALUES));
+      
+      tokenClassFetchedFromArray = StreamSupport.stream(array.spliterator(), false)
+              .findFirst()
+              .get().getClass();
+    }
+  }
 
   public static class FetchTheTokenAnnotator extends JCasAnnotator_ImplBase {
     @Override
@@ -524,6 +685,7 @@ public class JCasClassLoaderTest {
         }
         
         if (hideClassesPatterns.stream().anyMatch(name::matches)) {
+          System.out.printf("[%s] prevented access to hidden class: %s%n", id, name);
           throw new ClassNotFoundException(name);
         }