blob: ba0e4f0f193ab22bddc4406f4fd881fd2ca69f53 [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.fit.component.initialize;
import static org.apache.uima.fit.factory.ExternalResourceFactory.PREFIX_SEPARATOR;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.apache.uima.UimaContext;
import org.apache.uima.UimaContextAdmin;
import org.apache.uima.fit.component.ExternalResourceAware;
import org.apache.uima.fit.descriptor.ExternalResource;
import org.apache.uima.fit.descriptor.ExternalResourceLocator;
import org.apache.uima.fit.internal.ReflectionUtil;
import org.apache.uima.fit.internal.ResourceList;
import org.apache.uima.resource.ResourceAccessException;
import org.apache.uima.resource.ResourceInitializationException;
import org.apache.uima.resource.ResourceManager;
import org.apache.uima.resource.impl.ResourceManager_impl;
import org.springframework.beans.SimpleTypeConverter;
/**
* Configurator class for {@link ExternalResource} annotations.
*/
public final class ExternalResourceInitializer {
private static final Object INITIALIZED = new Object();
private static Map<Object, Object> initializedResources = new WeakHashMap<Object, Object>();
private ExternalResourceInitializer() {
// No instances
}
/**
* Configure a component from the given context.
* @param object
* the component.
* @param context
* the UIMA context.
*
* @param <T>
* the component type.
* @throws ResourceInitializationException
* if the external resource cannot be configured.
*/
public static <T> void initialize(T object, UimaContext context)
throws ResourceInitializationException {
configure(context, object.getClass(), object.getClass(), object);
}
/**
* Helper method for recursively configuring super-classes.
*
* @param <T>
* the component type.
* @param context
* the context containing the resource bindings.
* @param baseCls
* the class on which configuration started.
* @param cls
* the class currently being configured.
* @param object
* the object being configured.
* @throws ResourceInitializationException
* if required resources could not be bound.
*/
private static <T> void configure(UimaContext context, Class<?> baseCls, Class<?> cls, T object)
throws ResourceInitializationException {
if (cls.getSuperclass() != null) {
configure(context, baseCls, cls.getSuperclass(), object);
} else {
// Try to initialize the external resources only once, not for each step of the
// class hierarchy of a component.
initializeNestedResources(context);
}
for (Field field : cls.getDeclaredFields()) {
if (!ReflectionUtil.isAnnotationPresent(field, ExternalResource.class)) {
continue;
}
ExternalResource era = ReflectionUtil.getAnnotation(field, ExternalResource.class);
// Get the resource key. If it is a nested resource, also get the prefix.
String key = era.key();
if (key.length() == 0) {
key = field.getName();
}
if (object instanceof ExternalResourceAware) {
String prefix = ((ExternalResourceAware) object).getResourceName();
if (prefix != null) {
key = prefix + PREFIX_SEPARATOR + key;
}
}
// Obtain the resource
Object value = getResourceObject(context, key);
if (value instanceof ExternalResourceLocator) {
value = ((ExternalResourceLocator) value).getResource();
}
// Sanity checks
if (value == null && era.mandatory()) {
throw new ResourceInitializationException(new IllegalStateException("Mandatory resource ["
+ key + "] is not set on [" + baseCls + "]"));
}
// Now record the setting and optionally apply it to the given
// instance.
if (value != null) {
field.setAccessible(true);
try {
if (value instanceof ResourceList) {
// Value is a multi-valued resource
ResourceList resList = (ResourceList) value;
// We cannot do this in ResourceList because the resource doesn't have access to
// the UIMA context we use here. Resources are initialize with their own contexts
// by the UIMA framework!
List<Object> elements = new ArrayList<Object>();
for (int i = 0; i < resList.getSize(); i++) {
Object elementValue = getResourceObject(context, resList.getResourceName()
+ PREFIX_SEPARATOR + ResourceList.ELEMENT_KEY + "[" + i + "]");
elements.add(elementValue);
}
SimpleTypeConverter converter = new SimpleTypeConverter();
value = converter.convertIfNecessary(elements, field.getType());
}
try {
field.set(object, value);
} catch (IllegalAccessException e) {
throw new ResourceInitializationException(e);
}
}
finally {
field.setAccessible(false);
}
}
}
}
private static Object getResourceObject(UimaContext aContext, String aKey)
throws ResourceInitializationException {
Object value;
try {
value = aContext.getResourceObject(aKey);
} catch (ResourceAccessException e) {
throw new ResourceInitializationException(e);
}
return value;
}
/**
* Scan the context and initialize external resources injected into other external resources.
*
* @param aContext
* the UIMA context.
*/
private static void initializeNestedResources(UimaContext aContext)
throws ResourceInitializationException {
List<ExternalResourceAware> awareResources = new ArrayList<ExternalResourceAware>();
// Initialize the resources - each resource must only be initialized once. We remember
// if a resource has already been initialized in a weak hash map, so we automatically
// forget about resources that are garbage collected.
for (Object r : getResources(aContext)) {
synchronized (initializedResources) {
if (r instanceof ExternalResourceAware && !initializedResources.containsKey(r)) {
// Already mark the resource as initialized so we do not run into an
// endless recursive loop when initialize() is called again.
initializedResources.put(r, INITIALIZED);
initialize(r, aContext);
awareResources.add((ExternalResourceAware) r);
}
}
}
// Notify the resources after everything has been configured
for (ExternalResourceAware res : awareResources) {
res.afterResourcesInitialized();
}
}
/**
* Get all resources declared in the context.
*/
private static Collection<?> getResources(UimaContext aContext)
throws ResourceInitializationException {
if (!(aContext instanceof UimaContextAdmin)) {
return Collections.emptyList();
}
ResourceManager resMgr = ((UimaContextAdmin) aContext).getResourceManager();
if (!(resMgr instanceof ResourceManager_impl)) {
// Unfortunately there is not official way to access the list of resources. Thus we
// have to rely on the UIMA implementation details and access the internal resource
// map via reflection. If the resource manager is not derived from the default
// UIMA resource manager, then we cannot really do anything here.
throw new IllegalStateException("Unsupported resource manager implementation ["
+ resMgr.getClass() + "]");
}
return resMgr.getExternalResources();
}
}