/* * Copyright to the original author or authors. * * 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 org.rioproject.resolver; import org.rioproject.util.RioHome; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; /** * <p>A helper that provides utilities for obtaining and working with a * {@link org.rioproject.resolver.Resolver}.</p> * * <p>This utility uses the following approach to load a <code>Resolver</code> instance:</p> * <p>The location of the jar that contains an implementation of the <code>Resolver</code> interface is * determined in the following order:</p> * <ul> * <li>First checking if the <code>org.rioproject.resolver.jar</code> system property has been * declared. This property should contain the location of the resolver jar(s) needed to instantiate a * <code>Resolver</code>. * <li>If the <code>org.rioproject.resolver.jar</code> system property is not set, the * {@link org.rioproject.resolver.ResolverConfiguration} is used to obtain the resolver jar(s). If the * {@code ResolverConfiguration} cannot be loaded, or does not contain a jar property, the * <code>$RIO_HOME/lib/resolver/resolver-aether.jar</code> will be used * </ul> * <p>Refer to {@link ResolverHelper#getResolver} for details on determining the class to instantiate.</p> * * @author Dennis Reedy */ @SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes") public final class ResolverHelper { private static URLClassLoader resolverLoader; static final Logger logger = LoggerFactory.getLogger(ResolverHelper.class.getName()); private static final ResolverConfiguration resolverConfiguration = new ResolverConfiguration(); static { File resolverJar = new File(getResolverJarFile()); try { resolverLoader = new URLClassLoader(new URL[]{resolverJar.toURI().toURL()}, Thread.currentThread().getContextClassLoader()); } catch (MalformedURLException e) { logger.error("Creating ClassLoader to load {}", resolverJar.getPath(), e); } } /* * Disallow instantiation */ private ResolverHelper() { } /** * Resolve the classpath of the provided artifact, returning an array of {@link URL}s. * * @param artifact The artifact to resolve * @param resolver The {@link Resolver} to use * @param repositories The repositories to use for resolution * * @return The classpath for the artifact * * @throws ResolverException If there are exceptions resolving the artifact */ public static URL[] resolve(final String artifact, final Resolver resolver, final RemoteRepository[] repositories) throws ResolverException { if(logger.isDebugEnabled()) logger.debug("Using Resolver {}", resolver.getClass().getName()); List<URL> jars = new ArrayList<URL>(); if (artifact != null) { String[] artifactParts = artifact.split(" "); for(String artifactPart : artifactParts) { String[] classPath = resolver.getClassPathFor(artifactPart, repositories); for (String jar : classPath) { String s = null; File jarFile = new File(jar); if(jarFile.exists()) { s = jarFile.toURI().toString(); } else { logger.warn("{} NOT FOUND", jarFile.getPath()); } if(s!=null) { URL url; try { url = new URL(handleWindows(s)); } catch (MalformedURLException e) { throw new ResolverException("Invalid classpath element: "+s, e); } if(!jars.contains(url)) jars.add(url); } } } } if(logger.isDebugEnabled()) logger.debug("Artifact: {}, resolved jars {}", artifact, jars); return jars.toArray(new URL[jars.size()]); } /** * Provides a standard means for obtaining Resolver instances, using a * configurable provider. The resolver provider can be specified by providing * a resource named "META-INF/services/org.rioproject.resolver.Resolver" * containing the name of the provider class. If multiple resources with * that name are available, then the one used will be the first one returned * by ServiceLoader.load. * * @return An instance of the resolver provider * * @throws ResolverException if there are problems loading the resource */ public static Resolver getResolver() throws ResolverException { return getResolver(resolverLoader); } static String getResolverJarFile() { String resolverJarFile = resolverConfiguration.getResolverJar(); if(resolverJarFile!=null && !new File(resolverJarFile).exists()) { logger.warn("The configured resolver jar file [{}] does not exist, will attempt to load default resolver", resolverJarFile); resolverJarFile = null; } if(resolverJarFile==null) { String resolverJarPrefix = "resolver-aether"; String rioHome = RioHome.get(); if (rioHome == null || rioHome.length() == 0) { String message = String.format("Unable to determine the location of Rio home, this must be set " + "in order to load the default %s support", resolverJarPrefix); logger.error(message); throw new RuntimeException(message); } File resolverLibDir = new File(rioHome + File.separator + "lib" + File.separator + "resolver"); if (resolverLibDir.exists() && resolverLibDir.isDirectory()) { File[] files = resolverLibDir.listFiles(); if (files != null) { for (File file : files) { if (file.getName().startsWith(resolverJarPrefix)) { resolverJarFile = file.getPath(); break; } } } } else { String message = String.format("The resolver lib directory does not exist, tried using: %s", resolverLibDir.getPath()); logger.error(message); throw new RuntimeException(message); } } if(logger.isDebugEnabled()) logger.debug("Resolver JAR file: {}", resolverJarFile); return resolverJarFile; } /** * Provides a standard means for obtaining Resolver instances, using a * configurable provider. The resolver provider can be specified by providing * a resource named "META-INF/services/org.rioproject.resolver.Resolver" * containing the name of the provider class. If multiple resources with * that name are available, then the one used will be the first one returned * by ServiceLoader.load. * * @param cl The class loader to load resources and classes, and to pass when * constructing the provider. If null, uses the context class loader. * * @return An instance of the resolver provider * * @throws ResolverException if there are problems loading the resource */ public static Resolver getResolver(final ClassLoader cl) throws ResolverException { Resolver r; ClassLoader resourceLoader = (cl != null) ? cl : Thread.currentThread().getContextClassLoader(); try { r = doGetResolver(resourceLoader); if(logger.isDebugEnabled()) logger.debug("Selected Resolver: {}", (r==null?"No Resolver configuration found":r.getClass().getName())); if(r==null) { throw new ResolverException("No Resolver configuration found"); } if(r instanceof SettableResolver) { SettableResolver settableResolver = (SettableResolver) r; settableResolver.setRemoteRepositories(resolverConfiguration.getRemoteRepositories()); settableResolver.setFlatDirectories(resolverConfiguration.getFlatDirectories()); } if(logger.isDebugEnabled()) { StringBuilder message = new StringBuilder(); for(RemoteRepository rr : r.getRemoteRepositories()) { if(message.length()>0) message.append("\n"); message.append(rr); } logger.debug("Resolver repositories: {}\n{}", r.getRemoteRepositories().size(), message.toString()); } } catch (Exception e) { if(e instanceof ResolverException) throw (ResolverException)e; throw new ResolverException("Creating Resolver", e); } return r; } /* * Returns the Resolver using ServiceLoader.load. */ private static Resolver doGetResolver(final ClassLoader cl) throws IOException, ResolverException { Resolver resolver = null; ServiceLoader<Resolver> loader = ServiceLoader.load(Resolver.class, cl); if(logger.isDebugEnabled()) { StringBuilder sb = new StringBuilder(); int num = 0; for(Resolver r : loader) { if(sb.length()>0) sb.append(", "); sb.append(r.getClass().getName()); num++; } logger.debug("Found {} Resolvers: [{}]", num, sb.toString()); } for(Resolver r : loader) { if(r!=null) { resolver = r; break; } } return resolver; } /* * Convert windows path names if needed */ public static String handleWindows(final String s) { String newString = s; if (System.getProperty("os.name").startsWith("Windows")) { if(s.startsWith("/")) newString = s.substring(1, s.length()); if(s.startsWith("file:")) newString = s.replace('/', '\\'); } return newString; } }