blob: 97b07fea0b0bdc6d90dd021061255ec0edb697eb [file] [log] [blame]
/*
* 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.jcas.test;
import static java.util.Arrays.asList;
import static org.apache.uima.UIMAFramework.getResourceSpecifierFactory;
import static org.apache.uima.util.CasCreationUtils.createCas;
import static org.apache.uima.util.CasCreationUtils.mergeTypeSystems;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import org.apache.commons.io.IOUtils;
import org.apache.uima.UIMAFramework;
import org.apache.uima.UIMARuntimeException;
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.impl.BuiltinTypeKinds;
import org.apache.uima.jcas.JCas;
import org.apache.uima.jcas.cas.TOP;
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.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JCasFeatureOffsetTest {
static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
static final String BASE = "src/test/resources/JCasFeatureOffsetTest";
TypeSystemDescription tdBase;
TypeSystemDescription tdSubtype1;
TypeSystemDescription tdSubtype2;
TypeSystemDescription tdMerged;
ClassLoader clSubtype1;
ClassLoader clSubtype2;
ResourceManager rmBase;
ResourceManager rmSubtype1;
ResourceManager rmSubtype2;
@BeforeEach
void setup() throws Exception {
tdBase = loadTypeSystem("BaseDescriptor.xml");
tdSubtype1 = loadTypeSystem("SubtypeDescriptor1.xml");
tdSubtype2 = loadTypeSystem("SubtypeDescriptor2.xml");
tdMerged = mergeTypeSystems(asList(tdBase, tdSubtype1, tdSubtype2));
rmBase = UIMAFramework.newDefaultResourceManager();
clSubtype1 = createScopedJCasClassKoader("SubtypeDescriptor1-jcas/classes");
rmSubtype1 = UIMAFramework.newDefaultResourceManager();
rmSubtype1.setExtensionClassLoader(clSubtype1, true);
clSubtype2 = createScopedJCasClassKoader("SubtypeDescriptor2-jcas/classes");
rmSubtype2 = UIMAFramework.newDefaultResourceManager();
rmSubtype2.setExtensionClassLoader(clSubtype2, true);
}
@Test
void thatPreMergedTypeSystemWorks() throws Exception {
JCas jcas = createCas(tdMerged, null, null).getJCas();
AnalysisEngine ae1 = createTestEngine(rmSubtype1);
AnalysisEngine ae2 = createTestEngine(rmSubtype2);
ae1.process(jcas);
ae2.process(jcas);
}
@Test
void thatNotPreMergedTypeSystemsWithIsolatedLoadersWorks() throws Exception {
JCas jcas = createCas(tdBase, null, null).getJCas();
AnalysisEngine ae1 = createTestEngine(rmSubtype1);
AnalysisEngine ae2 = createTestEngine(rmSubtype2);
ae1.process(jcas);
ae2.process(jcas);
}
@Test
void thatRebindingFeaturesToDifferentIndexFails() throws Exception {
LOG.info("=== Initializing the JCas built-in classes");
createCas().getJCas();
// Bind callsite of featureX from rmSubtype1 to index 1
createCas(tdMerged, null, null, null, rmSubtype1).getJCas();
// Bind callsite of featureY from rmSubtype2 to index 2
createCas(tdMerged, null, null, null, rmSubtype2).getJCas();
// featureY from rmSubtype2 is bound to index 2 but in tdSubtype2, it would be index 1
// because featureX does not exist here -> FAIL
assertThatExceptionOfType(UIMARuntimeException.class).isThrownBy(() -> {
createCas(tdSubtype2, null, null, null, rmSubtype2).getJCas();
});
}
@Test
void thatRebindingFeaturesToDifferentIndexWorksWithIsolation() throws Exception {
LOG.info("=== Initializing the JCas built-in classes");
createCas().getJCas();
// Bind callsite of featureX from rmSubtype1 to index 1
createCas(tdMerged, null, null, null, rmSubtype1).getJCas();
// Bind callsite of featureY from rmSubtype2 to index 2
createCas(tdMerged, null, null, null, rmSubtype2).getJCas();
// Using an isolating classloader, we make sure that a new JCas wrapper class instance is
// used that is not bound yet - and we should not re-use this classloader
ClassLoader icl = new JCasIsolatingClassLoader(clSubtype2);
ResourceManager otherRmSubtype2 = UIMAFramework.newDefaultResourceManager();
otherRmSubtype2.setExtensionClassLoader(icl, true);
assertThatNoException().isThrownBy(() -> {
createCas(tdSubtype2, null, null, null, otherRmSubtype2).getJCas();
});
}
private AnalysisEngine createTestEngine(ResourceManager rm1)
throws ResourceInitializationException {
AnalysisEngineDescription aed = getResourceSpecifierFactory().createAnalysisEngineDescription();
aed.setPrimitive(true);
aed.setImplementationName(TestEngine.class.getName());
return UIMAFramework.produceAnalysisEngine(aed, rm1, null);
}
public static class TestEngine extends JCasAnnotator_ImplBase {
@Override
public void process(JCas aJCas) throws AnalysisEngineProcessException {
// Nothing to do
}
}
private TypeSystemDescription loadTypeSystem(String aPath)
throws InvalidXMLException, IOException {
return UIMAFramework.getXMLParser()
.parseTypeSystemDescription(new XMLInputSource(new File(BASE + "/" + aPath)));
}
private URLClassLoader createScopedJCasClassKoader(String aPath) throws MalformedURLException {
return new URLClassLoader(new URL[] { new File(BASE + "/" + aPath).toURI().toURL() });
}
public static class JCasIsolatingClassLoader extends ClassLoader {
public JCasIsolatingClassLoader(ClassLoader aParent) {
super(aParent);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> alreadyLoadedClazz = findLoadedClass(name);
if (alreadyLoadedClazz != null) {
return alreadyLoadedClazz;
}
Class<?> clazz = getParent().loadClass(name);
if (!TOP.class.isAssignableFrom(clazz)) {
return clazz;
}
Object typeName;
try {
Field typeNameField = clazz.getField("_TypeName");
typeName = typeNameField.get(null);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException
| IllegalAccessException e) {
return clazz;
}
if (BuiltinTypeKinds.creatableBuiltinJCas.contains(typeName)) {
return clazz;
}
String internalName = name.replace(".", "/") + ".class";
InputStream is = getParent().getResourceAsStream(internalName);
if (is == null) {
throw new ClassNotFoundException(name);
}
try {
byte[] bytes = IOUtils.toByteArray(is);
Class<?> cls = defineClass(name, bytes, 0, bytes.length);
if (cls.getPackage() == null) {
int packageSeparator = name.lastIndexOf('.');
if (packageSeparator != -1) {
String packageName = name.substring(0, packageSeparator);
definePackage(packageName, null, null, null, null, null, null, null);
}
}
return cls;
} catch (IOException ex) {
throw new ClassNotFoundException("Cannot load resource for class [" + name + "]", ex);
}
}
}
}
}