reckart commented on a change in pull request #46: URL: https://github.com/apache/uima-uimaj/pull/46#discussion_r443231498
########## File path: uimaj-core/src/test/java/org/apache/uima/cas/test/JCasClassLoaderTest.java ########## @@ -0,0 +1,292 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.uima.cas.test; + +import static java.util.Arrays.asList; +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.util.CasCreationUtils.createCas; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.io.IOUtils; +import org.apache.uima.analysis_component.Annotator_ImplBase; +import org.apache.uima.analysis_component.JCasAnnotator_ImplBase; +import org.apache.uima.analysis_engine.AnalysisEngine; +import org.apache.uima.analysis_engine.AnalysisEngineDescription; +import org.apache.uima.analysis_engine.AnalysisEngineProcessException; +import org.apache.uima.cas.CASException; +import org.apache.uima.cas.impl.CASImpl; +import org.apache.uima.jcas.JCas; +import org.apache.uima.resource.ResourceInitializationException; +import org.apache.uima.resource.ResourceManager; +import org.apache.uima.resource.metadata.TypeSystemDescription; +import org.apache.uima.util.InvalidXMLException; +import org.apache.uima.util.XMLInputSource; +import org.junit.Test; + +public class JCasClassLoaderTest { + @Test + public void thatCASCanBeDefinedWithoutJCasWrappersAndTheyComeInWithAnnotators() throws Exception { + 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) + .hiding("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*"); + + ClassLoader clForAddATokenAnnotator = new IsolatingClassloader("AddATokenAnnotator", rootCl) + .redefining("^.*AddATokenAnnotator$") + .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*"); + + ClassLoader clForFetchTheTokenAnnotator = new IsolatingClassloader("FetchTheTokenAnnotator", + rootCl) + .redefining("^.*FetchTheTokenAnnotator$") + .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*"); + + JCas jcas = makeJCas(clForCas); + AnalysisEngine addATokenAnnotator = makeAnalysisEngine(AddATokenAnnotator.class, + clForAddATokenAnnotator); + AnalysisEngine fetchTheTokenAnnotator = makeAnalysisEngine(FetchTheTokenAnnotator.class, + clForFetchTheTokenAnnotator); + + jcas.setDocumentText("test"); + + addATokenAnnotator.process(jcas); + fetchTheTokenAnnotator.process(jcas); + } + + @Test + public void thatAnnotatorsCanLocallyUseDifferentJCasWrappers() throws Exception { + ClassLoader rootCl = getClass().getClassLoader(); + + ClassLoader clForCas = new IsolatingClassloader("CAS", rootCl); + + ClassLoader clForAddATokenAnnotator = new IsolatingClassloader("AddATokenAnnotator", rootCl) + .redefining("^.*AddATokenAnnotator$") + .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*"); + + ClassLoader clForFetchTheTokenAnnotator = new IsolatingClassloader("FetchTheTokenAnnotator", + rootCl) + .redefining("^.*FetchTheTokenAnnotator$") + .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*"); + + JCas jcas = makeJCas(clForCas); + AnalysisEngine addATokenAnnotator = makeAnalysisEngine(AddATokenAnnotator.class, + clForAddATokenAnnotator); + AnalysisEngine fetchTheTokenAnnotator = makeAnalysisEngine(FetchTheTokenAnnotator.class, + clForFetchTheTokenAnnotator); + + jcas.setDocumentText("test"); + + addATokenAnnotator.process(jcas); + fetchTheTokenAnnotator.process(jcas); + } + + @Test + public void thatTypeSystemCanComeFromItsOwnClassLoader() throws Exception { + ClassLoader rootCl = getClass().getClassLoader(); + + ClassLoader clForTS = new IsolatingClassloader("TS", rootCl) + .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*"); + + ClassLoader clForCas = new IsolatingClassloader("CAS", rootCl); + + ClassLoader clForAddATokenAnnotator = new IsolatingClassloader("AddATokenAnnotator", rootCl) + .redefining("^.*AddATokenAnnotator$") + .delegating("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*", clForTS); + + ClassLoader clForFetchTheTokenAnnotator = new IsolatingClassloader("FetchTheTokenAnnotator", + rootCl) + .redefining("^.*FetchTheTokenAnnotator$") + .delegating("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*", clForTS); + + JCas jcas = makeJCas(clForCas); + AnalysisEngine addATokenAnnotator = makeAnalysisEngine(AddATokenAnnotator.class, + clForAddATokenAnnotator); + AnalysisEngine fetchTheTokenAnnotator = makeAnalysisEngine(FetchTheTokenAnnotator.class, + clForFetchTheTokenAnnotator); + + jcas.setDocumentText("test"); + + addATokenAnnotator.process(jcas); + fetchTheTokenAnnotator.process(jcas); + } + + /** + * 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 { + 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(); + } + + /** + * Creates a new analysis engine from the given class using the given classloader and sets it up + * so it used the given classloader to load its JCas wrappers. + */ + private AnalysisEngine makeAnalysisEngine(Class<? extends Annotator_ImplBase> aeClass, + ClassLoader cl) throws ResourceInitializationException, MalformedURLException { + ResourceManager resMgr = newDefaultResourceManager(); + resMgr.setExtensionClassPath(cl, "", false); + AnalysisEngineDescription desc = getResourceSpecifierFactory() + .createAnalysisEngineDescription(); + desc.setAnnotatorImplementationName(aeClass.getName()); + desc.setPrimitive(true); + return produceAnalysisEngine(desc, resMgr, null); + } + + public static class AddATokenAnnotator extends JCasAnnotator_ImplBase { + @Override + public void process(JCas aJCas) throws AnalysisEngineProcessException { + System.out.printf("%s class loader: %s%n", getClass().getName(), getClass().getClassLoader()); + + new Token(aJCas, 0, aJCas.getDocumentText().length()).addToIndexes(); + } + } + + public static class FetchTheTokenAnnotator extends JCasAnnotator_ImplBase { + @Override + public void process(JCas aJCas) throws AnalysisEngineProcessException { + System.out.printf("%s class loader: %s%n", getClass().getName(), getClass().getClassLoader()); + + List<Token> tokens = new ArrayList<>(); + aJCas.getAllIndexedFS(Token.class).forEachRemaining(tokens::add); + + assertThat(tokens).isNotEmpty(); + } + } + + /** + * Special ClassLoader that helps us modeling different class loader topologies. + */ + public static class IsolatingClassloader extends ClassLoader { + + private final Set<String> hideClassesPatterns = new HashSet<>(); + private final Set<String> redefineClassesPatterns = new HashSet<>(); + private final Map<String, ClassLoader> delegates = new LinkedHashMap<>(); + private final String id; + + private Map<String, Class<?>> loadedClasses = new HashMap<>(); + + public IsolatingClassloader(String name, ClassLoader parent) { + super(parent); + + id = name; + } + + public IsolatingClassloader hiding(String... patterns) + { + hideClassesPatterns.addAll(asList(patterns)); + return this; + } + + public IsolatingClassloader redefining(String... patterns) + { + hideClassesPatterns.addAll(asList(patterns)); Review comment: Indeed ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: [email protected]
