/*
* Copyright 2012
* Ubiquitous Knowledge Processing (UKP) Lab
* Technische Universität Darmstadt
*
* Licensed 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 de.tudarmstadt.ukp.dkpro.core.api.resources;
import static de.tudarmstadt.ukp.dkpro.core.api.resources.ResourceUtils.resolveLocation;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang.reflect.FieldUtils;
import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ivy.Ivy;
import org.apache.ivy.core.LogOptions;
import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.report.ArtifactDownloadReport;
import org.apache.ivy.core.report.ResolveReport;
import org.apache.ivy.core.resolve.ResolveOptions;
import org.apache.ivy.core.settings.IvySettings;
import org.apache.ivy.plugins.resolver.ChainResolver;
import org.apache.ivy.plugins.resolver.DependencyResolver;
import org.apache.ivy.plugins.resolver.IBiblioResolver;
import org.apache.ivy.util.Message;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.uima.fit.factory.ConfigurationParameterFactory;
import org.apache.uima.fit.internal.ReflectionUtil;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.PropertyPlaceholderHelper;
import de.tudarmstadt.ukp.dkpro.core.api.resources.internal.ApacheCommonsLoggingAdapter;
/**
* Base class for resource providers that produce a resource from some URL depending on changing
* parameters such as language.
* <p>
* A component using such a provider sets defaults and overrides. The defaults should be set to
* sensible values with which the component should be able to work out of the box. For example the
* {@link #LOCATION} may be set to "classpath:/resources/${language}/model.ser.gz".
* <p>
* The overrides should only be set if the user explicitly wants to override some settings.
* <p>
* Finally, parameters that may change, e.g. depending on the CAS content should be returned from
* {@code getProperties()}.
* <p>
* The {@link #LOCATION} may contain variables referring to any of the other settings, e.g.
* <code>"${language}"</code>.
* <p>
* It is possible a different default variant needs to be used depending on the language. This can
* be configured by placing a properties file in the classpath and setting its location using
* {@link #setDefaultVariantsLocation(String)} or by using {@link #setDefaultVariants(Properties)}.
* The key in the properties is the language and the value is used a default variant.
*
* @param <M>
* the kind of resource produced
*/
public abstract class ResourceObjectProviderBase<M>
implements HasResourceMetadata
{
private final Log log = LogFactory.getLog(ResourceObjectProviderBase.class);
public static final String PROP_REPO_OFFLINE = "dkpro.model.repository.offline";
public static final String PROP_REPO_ID = "dkpro.model.repository.id";
public static final String PROP_REPO_URL = "dkpro.model.repository.url";
public static final String PROP_REPO_CACHE = "dkpro.model.repository.cache";
public static final String FORCE_AUTO_LOAD = "forceAutoLoad";
private static final String DEFAULT_REPO_ID = "ukp-model-releases";
private static final String DEFAULT_REPO_URL = "http://zoidberg.ukp.informatik.tu-darmstadt.de/"
+ "artifactory/public-model-releases-local";
public static final String NOT_REQUIRED = "-=* NOT REQUIRED *=-";
/**
* The language.
*/
public static final String LANGUAGE = "language";
/**
* The variant. (optional)
*/
public static final String VARIANT = "variant";
/**
* The location from which the resource should be read. Variables in the location are resolved
* when {@link #configure()} is called.
*
* @see ResourceUtils#resolveLocation(String, Object, org.apache.uima.UimaContext)
*/
public static final String LOCATION = "location";
/**
* The group ID of the Maven artifact containing a resource. Variables in the location are
* resolved when {@link #configure()} is called. (optional)
*/
public static final String GROUP_ID = "groupId";
/**
* The artifact ID of the Maven artifact containing a resource. Variables in the location are
* resolved when {@link #configure()} is called. (optional)
*/
public static final String ARTIFACT_ID = "artifactId";
/**
* The version of the Maven artifact containing a resource. Variables in the location are
* resolved when {@link #configure()} is called. (optional)
*/
public static final String VERSION = "version";
public static final String PACKAGE = "package";
/**
* If this property is set to {@code true}, resources loaded through this provider are
* remembered by the provider using a weak reference. If the same resource is requested by
* another instance of this provider class, the same resource is returned.
*/
public static final String SHARABLE = "sharable";
public static final String CATCH_ALL = "*";
private Properties resourceMetaData;
private URL resourceUrl;
private URL initialResourceUrl;
private String lastModelLocation;
private M resource;
private Class<?> contextClass;
private Properties overrides = new Properties();
private Properties defaults = new Properties();
private Properties defaultVariants = null;
private String defaultVariantsLocation;
private Map<String, HasResourceMetadata> imports = new HashMap<String, HasResourceMetadata>();
private ExtensibleURLClassLoader loader = new ExtensibleURLClassLoader(getClass()
.getClassLoader());
private PropertyPlaceholderHelper pph = new PropertyPlaceholderHelper("${", "}", null, false);
private static Map<ResourceHandle, Object> cache = new WeakHashMap<ResourceHandle, Object>();
private Map<String, String> autoOverrides = new HashMap<>();
/**
* Maintain a reference to the handle for the currently loaded resource. This handle is used
* as a key in the resource cache and makes sure that the resource is not removed from the
* cache while it is still considered as "loaded" by this provider.
*/
@SuppressWarnings("unused")
private ResourceHandle resourceHandle;
{
init();
}
protected void init()
{
setDefault(GROUP_ID, "de.tudarmstadt.ukp.dkpro.core");
}
public void setOverride(String aKey, String aValue)
{
if (aValue == null) {
overrides.remove(aKey);
}
else {
overrides.setProperty(aKey, aValue);
}
}
public String getOverride(String aKey)
{
return (String) overrides.get(aKey);
}
public void removeOverride(String aKey)
{
overrides.remove(aKey);
}
public void setDefault(String aKey, String aValue)
{
if (aValue == null) {
defaults.remove(aKey);
}
else {
defaults.setProperty(aKey, aValue);
}
}
public String getDefault(String aKey)
{
return (String) defaults.get(aKey);
}
public void removeDefault(String aKey)
{
defaults.remove(aKey);
}
public void addImport(String aString, HasResourceMetadata aSource)
{
imports.put(aString, aSource);
}
public void removeImport(String aString)
{
imports.remove(aString);
}
/**
* Set the location in the classpath from where to load the language-dependent default variants
* properties file. The key in the properties is the language and the value is used a default
* variant.
*
* @param aLocation
* a location in the form "some/package/name/tool-default-variants.properties". This
* is always a classpath location. This location may not contain variables.
*/
public void setDefaultVariantsLocation(String aLocation)
{
defaultVariantsLocation = aLocation;
}
/**
* Sets language-dependent default variants. The key in the properties is the language and the
* value is used a default variant.
*
* @param aDefaultVariants
* the default variant per language
*/
public void setDefaultVariants(Properties aDefaultVariants)
{
if (aDefaultVariants.size() == 0) {
defaultVariants = null;
}
else {
defaultVariants = new Properties();
defaultVariants.putAll(aDefaultVariants);
}
}
/**
* Set an object which can be used to try finding a Maven POM from which resource version
* information could be extracted.
*
* @param aObject
* a context object, usually the object creating the provider.
*/
public void setContextObject(Object aObject)
{
setContextClass(aObject.getClass());
// Experimental: allow forcing any resource provider to allow sharing its resource
// Enable sharing the model between multiple instances of this AE. This is an experimental
// parameter for advanced users. Sharing the model can lead to unexpected results because
// some parameters affect the model when it is initialized. Thus, only the settings from the
// first instance using the model will initialize the model, but not the next instance which
// finds the already-loaded model and simply reuses it. Sharing models can also lead to
// unexpected results or crashes in multi-threaded environments.
// Allowed values: "true" and "false"
String key = "dkpro.core.resourceprovider.sharable."+aObject.getClass().getName();
if (System.getProperty(key) != null) {
setDefault(SHARABLE, System.getProperty(key));
}
}
/**
* Set a class which can be used to try finding a Maven POM from which resource version
* information could be extracted.
*
* @param aClass
* a context class, usually the class creating the provider.
*/
public void setContextClass(Class<?> aClass)
{
contextClass = aClass;
setDefault(PACKAGE, contextClass.getPackage().getName().replace('.', '/'));
}
public Class<?> getContextClass()
{
return contextClass;
}
public Map<String, String> getAutoOverrides()
{
return autoOverrides;
}
public void addAutoOverride(String aParameter, String aProperty)
{
autoOverrides.put(aParameter, aProperty);
}
public void applyAutoOverrides(Object aObject)
{
for (Field field : ReflectionUtil.getFields(aObject)) {
if (ConfigurationParameterFactory.isConfigurationParameterField(field)) {
String parameterName = ConfigurationParameterFactory
.getConfigurationParameterName(field);
// Check if there is an auto-override for this parameter
String property = autoOverrides.get(parameterName);
if (property != null) {
try {
setOverride(property, (String) FieldUtils.readField(field, aObject, true));
}
catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}
}
}
/**
* Tries to get the version of the required model from the dependency management section of the
* Maven POM belonging to the context object.
*
* @throws IOException
* if there was a problem loading the POM file
* @throws FileNotFoundException
* if no POM could be found
* @throws IllegalStateException
* if more than one POM was found, if the version information could not be found in
* the POM, or if no context object was set.
* @return the version of the required model.
*/
protected String getModelVersionFromMavenPom()
throws IOException
{
if (contextClass == null) {
throw new IllegalStateException("No context class specified");
}
// Get the properties and resolve the artifact coordinates
Properties props = getAggregatedProperties();
String modelArtifact = pph.replacePlaceholders(props.getProperty(ARTIFACT_ID), props);
String modelGroup = pph.replacePlaceholders(props.getProperty(GROUP_ID), props);
// Try to determine the location of the POM file belonging to the context object
URL url = contextClass.getResource(contextClass.getSimpleName() + ".class");
String classPart = contextClass.getName().replace(".", "/") + ".class";
String base = url.toString();
base = base.substring(0, base.length() - classPart.length());
List<URL> urls = new LinkedList<URL>();
String extraNotFoundInfo = "";
if ("file".equals(url.getProtocol()) && base.endsWith("target/classes/")) {
// This is an alternative strategy when running during a Maven build. In a normal
// Maven build, the Maven descriptor in META-INF is only created during the
// "package" phase, so we try looking in the project directory.
// See also: http://jira.codehaus.org/browse/MJAR-76
base = base.substring(0, base.length() - "target/classes/".length());
File pomFile = new File(new File(URI.create(base)), "pom.xml");
if (pomFile.exists()) {
urls.add(pomFile.toURI().toURL());
}
else {
extraNotFoundInfo = " Since it looks like you are running a Maven build, it POM "
+ "file was also searched for at [" + pomFile
+ "], but it doesn't exist there.";
}
}
if (urls.isEmpty()) {
// This is the default strategy supposed to look in the JAR
String moduleArtifactId = modelArtifact.split("-")[0];
String pomPattern = base + "META-INF/maven/" + modelGroup + "/" + moduleArtifactId +
"*/pom.xml";
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(pomPattern);
// Bail out if no POM was found
if (resources.length == 0) {
throw new FileNotFoundException("No POM file found using [" + pomPattern + "]"
+ extraNotFoundInfo);
}
for(Resource resource : resources){
urls.add(resource.getURL());
}
}
for(URL pomUrl : urls){
// Parser the POM
Model model;
try {
MavenXpp3Reader reader = new MavenXpp3Reader();
model = reader.read(pomUrl.openStream());
}
catch (XmlPullParserException e) {
throw new IOException(e);
}
// Extract the version of the model artifact
if ((model.getDependencyManagement() != null)
&& (model.getDependencyManagement().getDependencies() != null)) {
List<Dependency> deps = model.getDependencyManagement().getDependencies();
for (Dependency dep : deps) {
if (StringUtils.equals(dep.getGroupId(), modelGroup)
&& StringUtils.equals(dep.getArtifactId(), modelArtifact)) {
return dep.getVersion();
}
}
}
}
// Bail out if no version information for that artifact could be found
throw new IllegalStateException("No version information found.");
}
/**
* For use in test cases.
* @return the location of the model.
* @throws IOException
* if the language-dependent default variants location is set but cannot be read.
*/
protected String getModelLocation()
throws IOException
{
return getModelLocation(null);
}
protected String getLastModelLocation()
{
return lastModelLocation;
}
protected String getModelLocation(Properties aProperties)
throws IOException
{
Properties props = aProperties;
if (props == null) {
props = getAggregatedProperties();
}
try {
return pph.replacePlaceholders(props.getProperty(LOCATION), props);
}
catch (IllegalArgumentException e) {
throw new IllegalStateException("Unable to resolve the model location ["
+ props.getProperty(LOCATION) + "]: " + e.getMessage() + ". Possibly there is "
+ "no default model configured for the specified language ["
+ props.getProperty(LANGUAGE) + "] or the language is set incorrectly.");
}
}
/**
* Configure a resource using the current configuration. The resource can be fetched then using
* {@link #getResource()}.
* <p>
* Call this method after all configurations have been made. A already configured resource will
* only be recreated if the URL from which the resource is generated has changed due to
* configuration changes.
*
* @throws IOException
* if the resource cannot be created.
*/
public void configure()
throws IOException
{
boolean success = false;
Properties props = getAggregatedProperties();
String modelLocation = getModelLocation(props);
boolean modelLocationChanged = !StringUtils.equals(modelLocation, lastModelLocation);
lastModelLocation = modelLocation;
try {
if (modelLocation.startsWith(NOT_REQUIRED)) {
resourceUrl = null;
initialResourceUrl = null;
if (modelLocationChanged) {
log.info("Producing resource from thin air");
loadResource(props);
}
}
else {
URL initialUrl;
try {
if (FORCE_AUTO_LOAD.equals(System.getProperty(PROP_REPO_OFFLINE))) {
throw new IOException("Auto-loading forced");
}
initialUrl = resolveLocation(modelLocation, loader, null);
}
catch (IOException e) {
if (modelLocationChanged) {
// Try resolving the dependency and adding the stuff to the loader
try {
resolveDependency(props);
}
catch (Throwable re) {
// Ignore - if we cannot resolve, we cannot resolve. Re-throw the
// original exception
throw handleResolvingError(e, lastModelLocation, props);
}
try {
initialUrl = resolveLocation(modelLocation, loader, null);
}
catch (Throwable re) {
throw handleResolvingError(e, lastModelLocation, props);
}
}
else {
throw handleResolvingError(e, lastModelLocation, props);
}
}
if (!equals(initialResourceUrl, initialUrl)) {
initialResourceUrl = initialUrl;
resourceMetaData = new Properties();
resourceUrl = followRedirects(initialResourceUrl);
loadMetadata();
if (initialResourceUrl.equals(resourceUrl)) {
log.info("Producing resource from " + resourceUrl);
}
else {
log.info("Producing resource from [" + resourceUrl + "] redirected from [" + initialResourceUrl
+ "]");
}
loadResource(props);
}
}
success = true;
}
finally {
if (!success) {
resourceUrl = null;
resource = null;
}
}
}
private static boolean equals(URL aUrl1, URL aUrl2)
{
if (aUrl1 == aUrl2) {
return true;
}
if ((aUrl1 == null) || (aUrl2 == null)) {
return false;
}
return aUrl1.toString().equals(aUrl2.toString());
}
protected URL followRedirects(URL aUrl) throws IOException
{
URL url = aUrl;
// If the model points to a properties file, try to find a new location in that
// file. If that points to a properties file again, repeat the process.
while (url.getPath().endsWith(".properties")) {
Properties tmpResourceMetaData = PropertiesLoaderUtils.loadProperties(new UrlResource(
url));
// Values in the redirecting properties override values in the redirected-to
// properties - except LOCATION
resourceMetaData.remove(LOCATION);
mergeProperties(resourceMetaData, tmpResourceMetaData);
String redirect = resourceMetaData.getProperty(LOCATION);
if (redirect == null) {
throw new IOException("Model URL resolves to properties at [" + url
+ "] but no redirect property [" + LOCATION + "] found there.");
}
url = resolveLocation(redirect, loader, null);
}
return url;
}
protected void loadMetadata() throws IOException
{
Properties modelMetaData = null;
// Load resource meta data if present, look directly next to the resolved model
try {
String modelMetaDataLocation = getModelMetaDataLocation(resourceUrl.toString());
URL modelMetaDataUrl = resolveLocation(modelMetaDataLocation, loader, null);
modelMetaData = PropertiesLoaderUtils.loadProperties(new UrlResource(
modelMetaDataUrl));
}
catch (FileNotFoundException e) {
// Ignore
}
// Try in the again, this time derive the metadata location first an then resolve.
// This can help if the metadata is in another artifact, e.g. because the migration
// to proxy/resource mode is not completed yet and the provide still looks for the model
// and not for the properties file.
if (modelMetaData == null) {
try {
String modelMetaDataLocation = getModelMetaDataLocation(lastModelLocation);
URL modelMetaDataUrl = resolveLocation(modelMetaDataLocation, loader, null);
modelMetaData = PropertiesLoaderUtils.loadProperties(new UrlResource(
modelMetaDataUrl));
}
catch (FileNotFoundException e2) {
// If no metadata was found, just leave the properties empty.
}
}
// Values in the redirecting properties override values in the redirected-to
// properties.
if (modelMetaData != null) {
mergeProperties(resourceMetaData, modelMetaData);
}
}
private String getModelMetaDataLocation(String aLocation)
{
String baseLocation = aLocation;
if (baseLocation.toLowerCase().endsWith(".gz")) {
baseLocation = baseLocation.substring(0, baseLocation.length() - 3);
}
else if (baseLocation.toLowerCase().endsWith(".bz2")) {
baseLocation = baseLocation.substring(0, baseLocation.length() - 4);
}
String modelMetaDataLocation = FilenameUtils.removeExtension(baseLocation)
+ ".properties";
return modelMetaDataLocation;
}
@SuppressWarnings("unchecked")
protected synchronized void loadResource(Properties aProperties) throws IOException
{
boolean sharable = "true".equals(aProperties.getProperty(SHARABLE, "false"));
ResourceHandle handle = null;
resource = null;
// Check the cache
if (sharable) {
// We need to scan the cache manually because in the end we need to keep a reference
// to exactly the same key object that was used to store the resource in the cache.
handle = new ResourceHandle(getClass(), resourceUrl != null ? resourceUrl.toString()
: null);
for (Entry<ResourceHandle, Object> e : cache.entrySet()) {
if (handle.equals(e.getKey())) {
resourceHandle = e.getKey();
resource = (M) e.getValue();
log.info("Used resource from cache");
}
}
}
// If there was nothing in the cache or if the cache is disabled, produce new
if (resource == null) {
StopWatch sw = new StopWatch();
sw.start();
resource = produceResource(resourceUrl);
sw.stop();
log.info("Producing resource took " + sw.getTime() + "ms");
// If cache is enabled, update the cache
if (sharable) {
cache.put(handle, resource);
resourceHandle = handle;
}
}
}
/**
* Tries to figure out which artifact contains the desired resource, tries to acquire it and
* add it to the loader. The dependencyManagement information from the POM of the caller is
* taken into account if possible.
*
* @param aProps the properties.
* @throws IOException if dependencies cannot be resolved.
* @throws IllegalStateException if
*/
private void resolveDependency(Properties aProps)
throws IOException, IllegalStateException
{
Set<String> names = aProps.stringPropertyNames();
if (names.contains(ARTIFACT_ID) && names.contains(GROUP_ID)) {
String artifactId = pph.replacePlaceholders(aProps.getProperty(ARTIFACT_ID), aProps);
String groupId = pph.replacePlaceholders(aProps.getProperty(GROUP_ID), aProps);
String version = pph.replacePlaceholders(aProps.getProperty(VERSION, ""), aProps);
// Try getting better information about the model version.
try {
version = getModelVersionFromMavenPom();
}
catch (IOException e) {
// Ignore - this will be tried and reported again later by handleResolvingError
}
catch (IllegalStateException e) {
// Ignore - this will be tried and reported again later by handleResolvingError
}
// Register files with loader
try {
List<File> files = resolveWithIvy(groupId, artifactId, version);
for (File file : files) {
loader.addURL(file.toURI().toURL());
}
}
catch (ParseException e) {
throw new IllegalStateException(e);
}
}
}
protected DependencyResolver getModelResolver()
{
IBiblioResolver ukpModels = new IBiblioResolver();
ukpModels.setName(System.getProperty(PROP_REPO_ID, DEFAULT_REPO_ID));
ukpModels.setRoot(System.getProperty(PROP_REPO_URL, DEFAULT_REPO_URL));
ukpModels.setM2compatible(true);
return ukpModels;
}
/**
* Try to fetch an artifact and its dependencies from the UKP model repository or from
* Maven Central.
*
* @param aGroupId the group ID.
* @param aArtifactId the artifact ID.
* @param aVersion the version
* @return a list of dependencies.
* @throws ParseException if Ivy settings cannot be parsed.
* @throws IOException if the dependencies cannot be resolved.
*/
private List<File> resolveWithIvy(String aGroupId, String aArtifactId, String aVersion)
throws ParseException, IOException
{
if ("true".equals(System.getProperty(PROP_REPO_OFFLINE))) {
log.debug("Offline mode active - attempt to download missing resource automatically "
+ "is skipped.");
return Collections.emptyList();
}
// Configure Ivy
Message.setDefaultLogger(new ApacheCommonsLoggingAdapter(log));
IvySettings ivySettings = new IvySettings();
ivySettings.loadDefault();
ivySettings.configureRepositories(true);
ivySettings.configureDefaultVersionMatcher();
if (System.getProperties().containsKey(PROP_REPO_CACHE)) {
ivySettings.setDefaultCache(new File(System.getProperty(PROP_REPO_CACHE)));
}
// Add a resolver for the UKP model repository
DependencyResolver modelResolver = getModelResolver();
modelResolver.setSettings(ivySettings);
ivySettings.addResolver(modelResolver);
((ChainResolver) ivySettings.getResolver("main")).add(modelResolver);
// Initialize Ivy
Ivy ivy = Ivy.newInstance(ivySettings);
// Create a dummy module which has the desired artifact as a dependency
// The dummy module is kept in the Ivy cache only temporary, so make use it is unique
// using a UUID and make sure it is removed from the cache at the end.
UUID uuid = UUID.randomUUID();
ModuleRevisionId moduleId = ModuleRevisionId
.newInstance("dkpro", uuid.toString(), "working");
DefaultModuleDescriptor md = DefaultModuleDescriptor.newDefaultInstance(moduleId);
DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(md,
ModuleRevisionId.newInstance(aGroupId, aArtifactId, aVersion), false, false, true);
dd.addDependencyConfiguration("default", "default");
md.addDependency(dd);
ResolveReport report;
try {
// Resolve the temporary module
ResolveOptions options = new ResolveOptions();
if (log.isDebugEnabled()) {
options.setLog(LogOptions.LOG_DEFAULT);
}
else if (log.isInfoEnabled()) {
options.setLog(LogOptions.LOG_DOWNLOAD_ONLY);
}
else {
options.setLog(LogOptions.LOG_QUIET);
}
options.setConfs(new String[] { "default" });
report = ivy.resolve(md, options);
}
finally {
// Remove temporary module
String resid = ResolveOptions.getDefaultResolveId(md);
ivy.getResolutionCacheManager().getResolvedIvyFileInCache(moduleId).delete();
ivy.getResolutionCacheManager().getResolvedIvyPropertiesInCache(moduleId).delete();
ivy.getResolutionCacheManager().getConfigurationResolveReportInCache(resid, "default")
.delete();
}
// Get the artifact and all its transitive dependencies
List<File> files = new ArrayList<File>();
for (ArtifactDownloadReport rep : report.getAllArtifactsReports()) {
files.add(rep.getLocalFile());
}
return files;
}
private IOException handleResolvingError(Throwable aCause, String aLocation, Properties aProps)
{
StringBuilder sb = new StringBuilder();
Set<String> names = aProps.stringPropertyNames();
if (names.contains(ARTIFACT_ID) && names.contains(GROUP_ID)) {
String artifactId = pph.replacePlaceholders(aProps.getProperty(ARTIFACT_ID), aProps);
String groupId = pph.replacePlaceholders(aProps.getProperty(GROUP_ID), aProps);
String version = pph.replacePlaceholders(aProps.getProperty(VERSION, ""), aProps);
// Try getting better information about the model version.
String extraErrorInfo = "";
try {
version = getModelVersionFromMavenPom();
}
catch (IOException ex) {
extraErrorInfo = ExceptionUtils.getRootCauseMessage(ex);
}
catch (IllegalStateException ex) {
extraErrorInfo = ExceptionUtils.getRootCauseMessage(ex);
}
// Tell user how to add model dependency
sb.append("\nPlease make sure that [").append(artifactId).append(']');
if (StringUtils.isNotBlank(version)) {
sb.append(" version [").append(version).append(']');
}
sb.append(" is on the classpath.\n");
if (StringUtils.isNotBlank(version)) {
sb.append("If the version ").append(
"shown here is not available, try a recent version.\n");
sb.append('\n');
sb.append("If you are using Maven, add the following dependency to your pom.xml file:\n");
sb.append('\n');
sb.append("<dependency>\n");
sb.append(" <groupId>").append(groupId).append("</groupId>\n");
sb.append(" <artifactId>").append(artifactId).append("</artifactId>\n");
sb.append(" <version>").append(version).append("</version>\n");
sb.append("</dependency>\n");
sb.append('\n');
sb.append("Please consider that the model you are trying to use may not be publicly\n");
sb.append("distributable. Please refer to the DKPro Core User Guide for instructions\n");
sb.append("on how to package non-redistributable models.");
}
else {
sb.append(
"I was unable to determine which version of the desired model is "
+ "compatible with this component:\n").append(extraErrorInfo)
.append("\n");
}
}
if (NOT_REQUIRED.equals(aLocation)) {
return new IOException("Unable to load resource: \n"
+ ExceptionUtils.getRootCauseMessage(aCause) + "\n" + sb.toString());
}
else {
return new IOException("Unable to load resource [" + aLocation + "]: \n"
+ ExceptionUtils.getRootCauseMessage(aCause) + "\n" + sb.toString());
}
}
/**
* Get the currently configured resources. Before this can be used, {@link #configure()} needs
* to be called once or whenever the configuration changes. Mind that sub-classes may provide
* alternative configuration methods that may need to be used instead of {@link #configure()}.
*
* @return the currently configured resources.
*/
public M getResource()
{
return resource;
}
public ExtensibleURLClassLoader getClassLoader()
{
return loader;
}
/**
* Builds the aggregated configuration from defaults and overrides.
*
* @return the aggregated effective configuration.
* @throws IOException
* if the language-dependent default variants location is set but cannot be read.
*/
protected Properties getAggregatedProperties()
throws IOException
{
Properties defaultValues = new Properties(defaults);
Properties props = getProperties();
if (props != null) {
defaultValues.putAll(props);
}
Properties importedValues = new Properties(defaultValues);
for (Entry<String, HasResourceMetadata> e : imports.entrySet()) {
String value = e.getValue().getResourceMetaData().getProperty(e.getKey());
if (value != null) {
importedValues.setProperty(e.getKey(), value);
}
}
Properties overriddenValues = new Properties(importedValues);
overriddenValues.putAll(overrides);
// Load default variants if available and not already loaded
if ((defaultVariants == null) && (defaultVariantsLocation != null)) {
String dvl = pph.replacePlaceholders(defaultVariantsLocation, overriddenValues);
setDefaultVariants(PropertiesLoaderUtils.loadAllProperties(dvl));
}
// Apply default variant
String language = overriddenValues.getProperty(LANGUAGE);
if ((defaultVariants != null)) {
if (defaultVariants.containsKey(language)) {
defaultValues.setProperty(VARIANT, defaultVariants.getProperty(language));
}
else if (defaultVariants.containsKey(CATCH_ALL)) {
defaultValues.setProperty(VARIANT, defaultVariants.getProperty(CATCH_ALL));
}
}
return overriddenValues;
}
protected abstract Properties getProperties();
protected abstract M produceResource(URL aUrl)
throws IOException;
@Override
public Properties getResourceMetaData()
{
return resourceMetaData;
}
/**
* Copy all properties that not already exist in target from source.
*
* @param aTarget the properties to merge into.
* @param aSource the properties to merge from.
*/
protected void mergeProperties(Properties aTarget, Properties aSource)
{
for (Object key : aSource.keySet()) {
if (!aTarget.containsKey(key)) {
aTarget.put(key, aSource.get(key));
}
}
}
private static final class ResourceHandle
{
private String url;
private Class<?> owner;
public ResourceHandle(Class<?> aOwner, String aUrl)
{
owner = aOwner;
url = aUrl;
}
public String getUrl()
{
return url;
}
public Class<?> getOwner()
{
return owner;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = (prime * result) + ((owner == null) ? 0 : owner.hashCode());
result = (prime * result) + ((url == null) ? 0 : url.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ResourceHandle other = (ResourceHandle) obj;
if (owner == null) {
if (other.owner != null) {
return false;
}
}
else if (!owner.equals(other.owner)) {
return false;
}
if (url == null) {
if (other.url != null) {
return false;
}
}
else if (!url.equals(other.url)) {
return false;
}
return true;
}
}
private static final class ExtensibleURLClassLoader
extends URLClassLoader
{
public ExtensibleURLClassLoader(ClassLoader aParent)
{
super(new URL[0], aParent);
}
@Override
public void addURL(URL aUrl)
{
super.addURL(aUrl);
}
@Override
public URL getResource(String name)
{
if (FORCE_AUTO_LOAD.equals(System.getProperty(PROP_REPO_OFFLINE))) {
URL url = findResource(name);
if (url == null) {
url = super.getResource(name);
}
return url;
}
else {
return super.getResource(name);
}
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
if (FORCE_AUTO_LOAD.equals(System.getProperty(PROP_REPO_OFFLINE))) {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
c = findClass(name);
}
catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
}
if (c == null) {
c = super.loadClass(name, false);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
else {
return super.loadClass(name, resolve);
}
}
}
}