Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UIMA-6468] Problem with JCas classes with re-used across different type systems #208

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/*
* 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);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">



<name>typeSystemDescriptor</name>



<description/>



<version>1.0</version>



<vendor/>



<types>



<typeDescription>



<name>org.apache.uima.test.jcasfeatureoffsettest.Base</name>



<description/>



<supertypeName>uima.tcas.Annotation</supertypeName>



</typeDescription>


</types>



</typeSystemDescription>
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@



/* Apache UIMA v3 - First created by JCasGen Wed Jun 01 09:28:33 CEST 2022 */

package org.apache.uima.test.jcasfeatureoffsettest;



import org.apache.uima.cas.impl.CASImpl;
import org.apache.uima.cas.impl.TypeImpl;
import org.apache.uima.jcas.JCas;
import org.apache.uima.jcas.JCasRegistry;


import org.apache.uima.jcas.tcas.Annotation;


/**
* Updated by JCasGen Wed Jun 01 09:31:47 CEST 2022
* XML source: /Users/bluefire/git/uima-uimaj/uimaj-core/src/test/resources/JCasFeatureOffsetTest/SubtypeDescriptor1.xml
* @generated */
public class Base extends Annotation {

/** @generated
* @ordered
*/
@SuppressWarnings ("hiding")
public final static String _TypeName = "org.apache.uima.test.jcasfeatureoffsettest.Base";

/** @generated
* @ordered
*/
@SuppressWarnings ("hiding")
public final static int typeIndexID = JCasRegistry.register(Base.class);
/** @generated
* @ordered
*/
@SuppressWarnings ("hiding")
public final static int type = typeIndexID;
/** @generated
* @return index of the type
*/
@Override
public int getTypeIndexID() {return typeIndexID;}


/* *******************
* Feature Offsets *
* *******************/



/* Feature Adjusted Offsets */


/** Never called. Disable default constructor
* @generated */
@Deprecated
@SuppressWarnings ("deprecation")
protected Base() {/* intentionally empty block */}

/** Internal - constructor used by generator
* @generated
* @param casImpl the CAS this Feature Structure belongs to
* @param type the type of this Feature Structure
*/
public Base(TypeImpl type, CASImpl casImpl) {
super(type, casImpl);
readObject();
}

/** @generated
* @param jcas JCas to which this Feature Structure belongs
*/
public Base(JCas jcas) {
super(jcas);
readObject();
}


/** @generated
* @param jcas JCas to which this Feature Structure belongs
* @param begin offset to the begin spot in the SofA
* @param end offset to the end spot in the SofA
*/
public Base(JCas jcas, int begin, int end) {
super(jcas);
setBegin(begin);
setEnd(end);
readObject();
}

/**
* <!-- begin-user-doc -->
* Write your own initialization here
* <!-- end-user-doc -->
*
* @generated modifiable
*/
private void readObject() {/*default - does nothing empty block */}

}


Loading