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:43 UTC

[uima-uimaj] branch bugfix/UIMA-6287-FSArray.spliterator-fails-to-handle-PEAR-case created (now c181642)

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

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


      at c181642  [UIMA-6287] FSArray.spliterator() fails to handle PEAR case

This branch includes the following new commits:

     new c181642  [UIMA-6287] FSArray.spliterator() fails to handle PEAR case

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



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

Posted by re...@apache.org.
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);
         }