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

Issue #226: Provide SPI interfaces to locate descriptors #237

Merged
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
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
* [ ] PR adds/updates documentation

**Organizational**
- [ ] PR includes new dependencies.
- [ ] PR adds/updates dependencies.
<sub><sup>Only dependencies under [approved licenses](http://www.apache.org/legal/resolved.html#category-a) are allowed. LICENSE and NOTICE files in the respective modules where dependencies have been added as well as in the project root have been updated.</sup></sub>
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
.project
.settings
target
META-INF
checkpoint_synchPoint.xml
checkpoint_synchPoint.xml.prev
checkpoint.dat
Expand Down
52 changes: 52 additions & 0 deletions uima-doc-v3-users-guide/src/docs/asciidoc/uv3.spi.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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.

[[_uv3.custom_java_objects]]
= Type discovery via SPI

JCas types and associated type system descriptions can be made discoverable by UIMA using Java's
SPI mechanism.

NOTE: The core UIMA Java SDK currently only this mechanism to discover JCas classes. SPI-based
auto-discovery of type system descriptions is supported by uimaFIT 3.4.0 and higher.

SPI-based JCas class discovery is important in situation where multiple classloaders are used, e.g.
in OSGi environments. This is because JCas classes must be globally unique in the entire system
(with the exception of PEARs which can have their own JCas classes). So if JCas classes are to be
provided through different class loaders (e.g. OSGI bundle classloaders), they must be announced via
SPI, otherwise UIMA will not be able to reliably associated the JCas classes with their unique
classloader.

To announce JCas classes via SPI, create a file `META-INF/services/org.apache.uima.spi.JCasClassProvider`
and in the file, place implementations of the interface `org.apache.uima.spi.JCasClassProvider`, one
per line.

Here is a trivial example implementation of the interface that announces two JCas classes.

[source]
----
public class MyJCasClassProvider implements JCasClassProvider {

@Override
public List<Class<? extends TOP>> listJCasClasses() {
return asList(MyToken.class, MySentence.class);
}
}
----

More elaborate implementations might e.g. use uimaFIT to auto-detect types and if there is a JCas
class for any of these types, announce them.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ include::uv3.select.adoc[leveloffset=+1]

include::uv3.annotation_predicates.adoc[leveloffset=+1]

include::uv3.spi.adoc[leveloffset=+1]

include::uv3.custom_java_objects.adoc[leveloffset=+1]

include::uv3.logging.adoc[leveloffset=+1]
Expand Down
5 changes: 4 additions & 1 deletion uimaj-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -260,10 +260,13 @@
<exclude>src/test/resources/pearTests/*.pear</exclude>
<exclude>src/test/resources/pearTests/encodingTests/*</exclude>
<exclude>src/test/resources/SequencerTest/*.txt</exclude>
<exclude>src/test/resources/SerDes*/SavedInts.binary</exclude>
<exclude>src/test/resources/SerDes*/SavedInts.binary</exclude>
<exclude>src/test/resources/META-INF/services/org.apache.uima.spi.JCasClassProvider</exclude>
<!-- jcas classes generated -->
<exclude>src/test/java/aa/*.java</exclude>
<exclude>src/test/java/org/apache/uima/cas/test/*.java</exclude>
<exclude>src/test/java/org/apache/uima/spi/SpiToken.java</exclude>
<exclude>src/test/java/org/apache/uima/spi/SpiSentence.java</exclude>
<exclude>src/test/java/org/apache/lang/LanguagePair.java</exclude>
<exclude>src/test/java/sofa/test/CrossAnnotation.java</exclude>
<exclude>src/test/java/x/y/z/*.java</exclude>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ServiceLoader;

import org.apache.uima.UIMAFramework;
import org.apache.uima.UIMARuntimeException;
Expand All @@ -52,6 +53,7 @@
import org.apache.uima.internal.util.UIMAClassLoader;
import org.apache.uima.internal.util.WeakIdentityMap;
import org.apache.uima.jcas.cas.TOP;
import org.apache.uima.spi.JCasClassProvider;
import org.apache.uima.util.Level;
import org.apache.uima.util.Logger;

Expand Down Expand Up @@ -194,6 +196,8 @@ public abstract class FSClassRegistry { // abstract to prevent instantiating; th
private static final WeakIdentityMap<ClassLoader, Map<String, JCasClassInfo>> cl_to_type2JCas = WeakIdentityMap
.newHashMap(); // identity: key is classloader
private static final WeakIdentityMap<ClassLoader, StackTraceElement[]> cl_to_type2JCasStacks;
private static final WeakIdentityMap<ClassLoader, Map<String, Class<? extends TOP>>> cl_to_spiJCas = WeakIdentityMap
.newHashMap();

// private static final Map<ClassLoader, Map<String, JCasClassInfo>> cl_4pears_to_type2JCas =
// Collections.synchronizedMap(new IdentityHashMap<>()); // identity: key is classloader
Expand Down Expand Up @@ -641,7 +645,19 @@ static JCasClassInfo maybeCreateJCasClassInfo(TypeImpl ti, ClassLoader cl,
}

public static JCasClassInfo createJCasClassInfo(TypeImpl ti, ClassLoader cl, Lookup lookup) {
Class<? extends TOP> clazz = maybeLoadJCas(ti, cl);
Lookup actualLookup = lookup;

// First we try the local classloader - this is necessary because it might be a PEAR situation
Class<? extends TOP> clazz = maybeLoadLocalJCas(ti, cl);

// If the local classloader does not have the JCas wrapper, we try the SPI
if (clazz == null) {
Map<String, Class<? extends TOP>> spiJCasClasses = loadJCasClassesFromSPI(cl);
clazz = spiJCasClasses.get(ti.getJCasClassName());
if (clazz != null) {
actualLookup = getLookup(clazz.getClassLoader());
}
}

if (null == clazz || !TOP.class.isAssignableFrom(clazz)) {
return null;
Expand All @@ -663,7 +679,8 @@ public static JCasClassInfo createJCasClassInfo(TypeImpl ti, ClassLoader cl, Loo
return null;
}
}
return createJCasClassInfo(clazz, ti, jcasType, lookup);

return createJCasClassInfo(clazz, ti, jcasType, actualLookup);
}

// static AtomicLong time = IS_TIME_AUGMENT_FEATURES ? new AtomicLong(0) : null;
Expand Down Expand Up @@ -910,27 +927,43 @@ private static String getAllSuperTypeNames(TypeImpl ti) {
*
* Synchronization: done outside this class
*
* @param typeName
* -
* @param cl
* the class loader to use
* @return the loaded / resolved class
*/
// @formatter:on
private static Class<? extends TOP> maybeLoadJCas(TypeImpl ti, ClassLoader cl) {
Class<? extends TOP> clazz = null;
@SuppressWarnings("unchecked")
private static Class<? extends TOP> maybeLoadLocalJCas(TypeImpl ti, ClassLoader cl) {
String className = ti.getJCasClassName();

try {
clazz = (Class<? extends TOP>) Class.forName(className, true, cl);
return (Class<? extends TOP>) Class.forName(className, true, cl);
} catch (ClassNotFoundException e) {
// Class not found is normal, if there is no JCas for this class
return clazz;
return null;
} catch (ExceptionInInitializerError e) {
throw new RuntimeException("Exception while loading " + className, e);
}
}

static Map<String, Class<? extends TOP>> loadJCasClassesFromSPI(ClassLoader cl) {
synchronized (cl_to_spiJCas) {
Map<String, Class<? extends TOP>> spiJCas = cl_to_spiJCas.get(cl);
if (spiJCas != null) {
return spiJCas;
}

return clazz;
Map<String, Class<? extends TOP>> spiJCasClasses = new LinkedHashMap<>();
ServiceLoader<JCasClassProvider> loader = ServiceLoader.load(JCasClassProvider.class, cl);
loader.forEach(provider -> {
List<Class<? extends TOP>> list = provider.listJCasClasses();
if (list != null) {
list.forEach(item -> spiJCasClasses.put(item.getName(), item));
}
});
cl_to_spiJCas.put(cl, spiJCasClasses);

return spiJCasClasses;
}
}

// SYNCHRONIZED
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.spi;

import java.util.List;

import org.apache.uima.jcas.cas.TOP;

public interface JCasClassProvider {
List<Class<? extends TOP>> listJCasClasses();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@
package org.apache.uima.cas.impl;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;

import java.util.Map;

import org.apache.uima.UIMAFramework;
import org.apache.uima.jcas.JCas;
import org.apache.uima.jcas.cas.TOP;
import org.apache.uima.resource.ResourceManager;
import org.apache.uima.spi.SpiSentence;
import org.apache.uima.spi.SpiToken;
import org.apache.uima.util.CasCreationUtils;
import org.apache.uima.util.Level;
import org.junit.Before;
Expand Down Expand Up @@ -84,6 +90,16 @@ public void thatCreatingResourceManagersWithExtensionPathDoesNotFillUpCache() th
}
}

@Test
public void thatJCasClassesCanBeLoadedThroughSPI() throws Exception {
Map<String, Class<? extends TOP>> jcasClasses = FSClassRegistry
.loadJCasClassesFromSPI(getClass().getClassLoader());

assertThat(jcasClasses).containsOnly( //
entry(SpiToken.class.getName(), SpiToken.class), //
entry(SpiSentence.class.getName(), SpiSentence.class));
}

private void assertRegisteredClassLoaders(int aExpectedCount, String aDescription) {
if (FSClassRegistry.clToType2JCasSize() > aExpectedCount) {
FSClassRegistry.log_registered_classloaders(Level.INFO);
Expand Down
Loading