Skip to content

Commit

Permalink
Issue #385: Allow import of type systems published through SPI
Browse files Browse the repository at this point in the history
- Enhance the Import_impl to look up descriptors via the type system description SPI
- Bit of cleaning up in related classes
  • Loading branch information
reckart committed Sep 26, 2024
1 parent 69ebb88 commit 086552f
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.WeakHashMap;

Expand Down Expand Up @@ -81,14 +80,14 @@ private TypeSystemDescriptionFactory() {
*/
public static TypeSystemDescription createTypeSystemDescription(String... descriptorNames) {

TypeSystemDescription typeSystem = new TypeSystemDescription_impl();
List<Import> imports = new ArrayList<>();
for (String descriptorName : descriptorNames) {
Import imp = new Import_impl();
var typeSystem = new TypeSystemDescription_impl();
var imports = new ArrayList<Import>();
for (var descriptorName : descriptorNames) {
var imp = new Import_impl();
imp.setName(descriptorName);
imports.add(imp);
}
Import[] importArray = new Import[imports.size()];
var importArray = new Import[imports.size()];
typeSystem.setImports(imports.toArray(importArray));
return typeSystem;
}
Expand All @@ -103,14 +102,14 @@ public static TypeSystemDescription createTypeSystemDescription(String... descri
public static TypeSystemDescription createTypeSystemDescriptionFromPath(
String... descriptorURIs) {

TypeSystemDescription typeSystem = new TypeSystemDescription_impl();
List<Import> imports = new ArrayList<>();
for (String descriptorURI : descriptorURIs) {
Import imp = new Import_impl();
var typeSystem = new TypeSystemDescription_impl();
var imports = new ArrayList<Import>();
for (var descriptorURI : descriptorURIs) {
var imp = new Import_impl();
imp.setLocation(descriptorURI);
imports.add(imp);
}
Import[] importArray = new Import[imports.size()];
var importArray = new Import[imports.size()];
typeSystem.setImports(imports.toArray(importArray));
return typeSystem;
}
Expand All @@ -127,12 +126,12 @@ public static TypeSystemDescription createTypeSystemDescriptionFromPath(
public static TypeSystemDescription createTypeSystemDescription()
throws ResourceInitializationException {

ClassLoader cl = ClassLoaderUtils.findClassloader();
TypeSystemDescription tsd = typeDescriptorByClassloader.get(cl);
var cl = ClassLoaderUtils.findClassloader();
var tsd = typeDescriptorByClassloader.get(cl);
if (tsd == null) {
synchronized (CREATE_LOCK) {
ResourceManager resMgr = ResourceManagerFactory.newResourceManager();
List<TypeSystemDescription> tsdList = new ArrayList<>();
var resMgr = ResourceManagerFactory.newResourceManager();
var tsdList = new ArrayList<TypeSystemDescription>();

loadTypeSystemDescriptionsFromScannedLocations(tsdList, resMgr);
loadTypeSystemDescriptionsFromSPIs(tsdList);
Expand All @@ -147,9 +146,9 @@ public static TypeSystemDescription createTypeSystemDescription()

static void loadTypeSystemDescriptionsFromScannedLocations(List<TypeSystemDescription> tsdList,
ResourceManager aResMgr) throws ResourceInitializationException {
for (String location : scanTypeDescriptors()) {
for (var location : scanTypeDescriptors()) {
try {
TypeSystemDescription description = typeDescriptors.get(location);
var description = typeDescriptors.get(location);

if (description == PLACEHOLDER) {
// If the description has not yet been loaded, load it
Expand All @@ -169,10 +168,9 @@ static void loadTypeSystemDescriptionsFromScannedLocations(List<TypeSystemDescri
}

static void loadTypeSystemDescriptionsFromSPIs(List<TypeSystemDescription> tsdList) {
ServiceLoader<TypeSystemDescriptionProvider> loader = ServiceLoader
.load(TypeSystemDescriptionProvider.class);
var loader = ServiceLoader.load(TypeSystemDescriptionProvider.class);
loader.forEach(provider -> {
for (TypeSystemDescription desc : provider.listTypeSystemDescriptions()) {
for (var desc : provider.listTypeSystemDescriptions()) {
tsdList.add(desc);
LOG.debug("Loaded SPI-provided type system at [{}]", desc.getSourceUrlString());
}
Expand All @@ -191,8 +189,8 @@ static void loadTypeSystemDescriptionsFromSPIs(List<TypeSystemDescription> tsdLi
public static String[] scanTypeDescriptors() throws ResourceInitializationException {

synchronized (SCAN_LOCK) {
ClassLoader cl = ClassLoaderUtils.findClassloader();
String[] typeDescriptorLocations = typeDescriptorLocationsByClassloader.get(cl);
var cl = ClassLoaderUtils.findClassloader();
var typeDescriptorLocations = typeDescriptorLocationsByClassloader.get(cl);

if (typeDescriptorLocations == null) {
typeDescriptorLocations = scanDescriptors(MetaDataType.TYPE_SYSTEM);
Expand All @@ -210,10 +208,10 @@ private static void internTypeDescriptorLocations(String[] typeDescriptorLocatio
// We "intern" the location strings because we will use them as keys in the WeakHashMap
// caching the parsed type systems. As part of this process, we put a PLACEHOLDER into the
// map which is replaced when the type system is actually loaded
Map<String, String> locationStrings = new HashMap<>();
var locationStrings = new HashMap<String, String>();
typeDescriptors.keySet().stream().forEach(loc -> locationStrings.put(loc, loc));
for (int i = 0; i < typeDescriptorLocations.length; i++) {
String existingLocString = locationStrings.get(typeDescriptorLocations[i]);
var existingLocString = locationStrings.get(typeDescriptorLocations[i]);
if (existingLocString == null) {
typeDescriptors.put(typeDescriptorLocations[i], PLACEHOLDER);
locationStrings.put(typeDescriptorLocations[i], typeDescriptorLocations[i]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
package org.apache.uima.resource.metadata.impl;

import static java.util.Arrays.asList;
import static java.util.Collections.newSetFromMap;
import static java.util.stream.Collectors.toList;

import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
Expand All @@ -39,7 +39,6 @@
import org.apache.uima.resource.impl.ResourceManager_impl;
import org.apache.uima.resource.metadata.Import;
import org.apache.uima.resource.metadata.MetaDataObject;
import org.apache.uima.resource.metadata.TypeSystemDescription;
import org.apache.uima.util.InvalidXMLException;
import org.apache.uima.util.XMLInputSource;
import org.apache.uima.util.XMLizable;
Expand Down Expand Up @@ -82,11 +81,7 @@ void resolveImports(DESCRIPTOR aDesc, ResourceManager aResourceManager)
* {@code null} then a new default resource manager is created.
* @throws InvalidXMLException
* if an import could not be processed.
* @deprecated Exists only to support a deprecated methods like
* {@link TypeSystemDescription#resolveImports(Collection, ResourceManager)}.
* @forRemoval 4.0.0
*/
@Deprecated(since = "3.3.0")
void resolveImports(DESCRIPTOR aDesc, Collection<String> aAlreadyImportedURLs,
ResourceManager aResourceManager) throws InvalidXMLException {
DescriptorAdapter<DESCRIPTOR, COLLECTIBLE> wrapper = adapterFactory.apply(aDesc);
Expand All @@ -95,23 +90,23 @@ void resolveImports(DESCRIPTOR aDesc, Collection<String> aAlreadyImportedURLs,
return;
}

Set<COLLECTIBLE> originalTypes = Collections.newSetFromMap(new IdentityHashMap<>());
Set<COLLECTIBLE> originalTypes = newSetFromMap(new IdentityHashMap<>());
originalTypes.addAll(asList(wrapper.getCollectibles()));

ResourceManager resourceManager = aResourceManager;
var resourceManager = aResourceManager;
if (aResourceManager == null) {
resourceManager = UIMAFramework.newDefaultResourceManager();
}

Set<String> alreadyImportedURLs = new HashSet<>();
Deque<String> stack = new LinkedList<>();
var alreadyImportedURLs = new HashSet<String>();
var stack = new LinkedList<String>();

if (aAlreadyImportedURLs != null) {
alreadyImportedURLs.addAll(aAlreadyImportedURLs);
aAlreadyImportedURLs.forEach(stack::push);
}

Map<Key, COLLECTIBLE> collectedObjects = new LinkedHashMap<>();
var collectedObjects = new LinkedHashMap<Key, COLLECTIBLE>();

stack.push(wrapper.unwrap().getSourceUrlString());
resolveImports(wrapper, new HashSet<>(), collectedObjects, stack, resourceManager);
Expand Down Expand Up @@ -143,20 +138,17 @@ private void resolveImports(DescriptorAdapter<DESCRIPTOR, COLLECTIBLE> aWrapper,
Set<String> aAlreadyVisited, Map<Key, COLLECTIBLE> aAllCollectedObjects,
Deque<String> aStack, ResourceManager aResourceManager) throws InvalidXMLException {

collectAll(aWrapper, aAllCollectedObjects);

if (aAlreadyVisited.contains(aWrapper.unwrap().getSourceUrlString())) {
collectAll(aWrapper, aAllCollectedObjects);
return;
}

Import[] imports = aWrapper.getImports();

var imports = aWrapper.getImports();
if (imports.length == 0) {
collectAll(aWrapper, aAllCollectedObjects);
return;
}

collectAll(aWrapper, aAllCollectedObjects);

Map<String, XMLizable> importCache = ((ResourceManager_impl) aResourceManager).getImportCache();
for (var imp : imports) {
// make sure Import's relative path base is set, to allow for users who create new import
Expand All @@ -170,7 +162,7 @@ private void resolveImports(DescriptorAdapter<DESCRIPTOR, COLLECTIBLE> aWrapper,
continue;
}

URL absUrl = imp.findAbsoluteUrl(aResourceManager);
var absUrl = imp.findAbsoluteUrl(aResourceManager);
String absUrlString = absUrl.toString();

// Loop cancellation - skip imports of descriptors that lie on the path from the
Expand All @@ -182,10 +174,10 @@ private void resolveImports(DescriptorAdapter<DESCRIPTOR, COLLECTIBLE> aWrapper,
aStack.push(absUrlString);

synchronized (importCache) {
DescriptorAdapter<DESCRIPTOR, COLLECTIBLE> importedTSAdapter = adapterFactory
var importedDescriptionAdapter = adapterFactory
.apply(getOrLoadDescription(imp, absUrl, importCache, aWrapper));

resolveImports(importedTSAdapter, aAlreadyVisited, aAllCollectedObjects, aStack,
resolveImports(importedDescriptionAdapter, aAlreadyVisited, aAllCollectedObjects, aStack,
aResourceManager);
}

Expand All @@ -208,7 +200,7 @@ private DESCRIPTOR getOrLoadDescription(Import aImport, URL aAbsUrl,
throws InvalidXMLException {
Class<DESCRIPTOR> descClass = aWrapper.getDescriptorClass();

XMLizable cachedDescriptor = aImportCache.get(aAbsUrl.toString());
var cachedDescriptor = aImportCache.get(aAbsUrl.toString());
if (descClass.isInstance(cachedDescriptor)) {
return descClass.cast(cachedDescriptor);
}
Expand All @@ -219,9 +211,9 @@ private DESCRIPTOR getOrLoadDescription(Import aImport, URL aAbsUrl,
private DESCRIPTOR loadDescriptor(Import aImport, URL aAbsUrl,
Map<String, XMLizable> aImportCache, ParserFunction<DESCRIPTOR> aParserFunction)
throws InvalidXMLException {
String urlString = aAbsUrl.toString();
var urlString = aAbsUrl.toString();
try {
XMLInputSource input = new XMLInputSource(aAbsUrl);
var input = new XMLInputSource(aAbsUrl);
DESCRIPTOR descriptor = aParserFunction.apply(input);
aImportCache.put(urlString, descriptor);
return descriptor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ServiceLoader;

import org.apache.uima.UIMAFramework;
import org.apache.uima.resource.ResourceManager;
import org.apache.uima.resource.metadata.Import;
import org.apache.uima.spi.TypeSystemDescriptionProvider;
import org.apache.uima.util.InvalidXMLException;
import org.apache.uima.util.Level;
import org.apache.uima.util.XMLParser;
Expand Down Expand Up @@ -54,7 +56,6 @@ public class Import_impl extends MetaDataObject_impl implements Import {
* the value if the evaluation succeeds (later fetches may not have the settings defined!) Leave
* value unmodified if any settings are undefined.
*/

@Override
public String getName() {
if (mName != null && mName.contains("${")) {
Expand Down Expand Up @@ -98,38 +99,65 @@ public void setSuffix(String suffix) {
public URL findAbsoluteUrl(ResourceManager aResourceManager) throws InvalidXMLException {
String location, name;
if ((location = getLocation()) != null) {
try {
URL url = new URL(getRelativePathBase(), location);
UIMAFramework.getLogger(this.getClass()).logrb(Level.CONFIG, this.getClass().getName(),
"findAbsoluteUrl", LOG_RESOURCE_BUNDLE, "UIMA_import_by__CONFIG",
new Object[] { "location", url });
return url;
} catch (MalformedURLException e) {
throw new InvalidXMLException(InvalidXMLException.MALFORMED_IMPORT_URL,
new Object[] { location, getSourceUrlString() }, e);
}
} else if ((name = getName()) != null) {
String filename = name.replace('.', '/') + byNameSuffix;
URL url;
try {
url = aResourceManager.resolveRelativePath(filename);
UIMAFramework.getLogger(this.getClass()).logrb(Level.CONFIG, this.getClass().getName(),
"findAbsoluteUrl", LOG_RESOURCE_BUNDLE, "UIMA_import_by__CONFIG",
new Object[] { "name", url });
} catch (MalformedURLException e) {
throw new InvalidXMLException(InvalidXMLException.IMPORT_BY_NAME_TARGET_NOT_FOUND,
new Object[] { filename, getSourceUrlString() }, e);
}
if (url == null) {
throw new InvalidXMLException(InvalidXMLException.IMPORT_BY_NAME_TARGET_NOT_FOUND,
new Object[] { filename, getSourceUrlString() });
return findResourceUrlByLocation(location);
}

if ((name = getName()) != null) {
return findResouceUrlByName(aResourceManager, name);
}

// no name or location -- this should be caught at XML parse time but we still need to
// check here in case object was modified or was created programmatically.
throw new InvalidXMLException(InvalidXMLException.IMPORT_MUST_HAVE_NAME_XOR_LOCATION,
new Object[] { getSourceUrlString() });
}

private URL findResouceUrlByName(ResourceManager aResourceManager, String name)
throws InvalidXMLException {
var filename = name.replace('.', '/') + byNameSuffix;
URL url;

// Try loading through the classpath
try {
url = aResourceManager.resolveRelativePath(filename);
UIMAFramework.getLogger(this.getClass()).logrb(Level.CONFIG, this.getClass().getName(),
"findAbsoluteUrl", LOG_RESOURCE_BUNDLE, "UIMA_import_by__CONFIG",
new Object[] { "name", url });
} catch (MalformedURLException e) {
throw new InvalidXMLException(InvalidXMLException.IMPORT_BY_NAME_TARGET_NOT_FOUND,
new Object[] { filename, getSourceUrlString() }, e);
}

// If that fails, try loading through the SPIs
if (url == null) {
var providers = ServiceLoader.load(TypeSystemDescriptionProvider.class);
for (var provider : providers) {
var maybeTypeSystemUrl = provider.findTypeSystemUrl(name);
if (maybeTypeSystemUrl.isPresent()) {
url = maybeTypeSystemUrl.get();
break;
}
}
}

if (url == null) {
throw new InvalidXMLException(InvalidXMLException.IMPORT_BY_NAME_TARGET_NOT_FOUND,
new Object[] { filename, getSourceUrlString() });
}

return url;
}

private URL findResourceUrlByLocation(String location) throws InvalidXMLException {
try {
var url = new URL(getRelativePathBase(), location);
UIMAFramework.getLogger(this.getClass()).logrb(Level.CONFIG, this.getClass().getName(),
"findAbsoluteUrl", LOG_RESOURCE_BUNDLE, "UIMA_import_by__CONFIG",
new Object[] { "location", url });
return url;
} else {
// no name or location -- this should be caught at XML parse time but we still need to
// check here in case object was modified or was created progrmatically.
throw new InvalidXMLException(InvalidXMLException.IMPORT_MUST_HAVE_NAME_XOR_LOCATION,
new Object[] { getSourceUrlString() });
} catch (MalformedURLException e) {
throw new InvalidXMLException(InvalidXMLException.MALFORMED_IMPORT_URL,
new Object[] { location, getSourceUrlString() }, e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.apache.uima.resource.metadata.impl;

import static org.apache.uima.resource.metadata.Import.EMPTY_IMPORTS;

import java.util.Collection;

import org.apache.uima.UIMAFramework;
Expand All @@ -43,7 +45,7 @@ public Import[] getImports() {

@Override
public void clearImports() {
delegate.setImports(Import.EMPTY_IMPORTS);
delegate.setImports(EMPTY_IMPORTS);
}

@Override
Expand Down
Loading

0 comments on commit 086552f

Please sign in to comment.