001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.discovery.tools;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.util.Properties;
022
023import org.apache.commons.discovery.DiscoveryException;
024import org.apache.commons.discovery.Resource;
025import org.apache.commons.discovery.ResourceIterator;
026import org.apache.commons.discovery.resource.ClassLoaders;
027import org.apache.commons.discovery.resource.DiscoverResources;
028
029/**
030 * Mechanisms to locate and load a class.
031 *
032 * The load methods locate a class only.
033 * The find methods locate a class and verify that the
034 * class implements an given interface or extends a given class.
035 */
036public class ResourceUtils {
037
038    /**
039     * Get package name.
040     *
041     * Not all class loaders 'keep' package information,
042     * in which case Class.getPackage() returns null.
043     * This means that calling Class.getPackage().getName()
044     * is unreliable at best.
045     *
046     * @param clazz The class from which the package has to be extracted
047     * @return The string representation of the input class package
048     */
049    public static String getPackageName(Class<?> clazz) {
050        Package clazzPackage = clazz.getPackage();
051        String packageName;
052        if (clazzPackage != null) {
053            packageName = clazzPackage.getName();
054        } else {
055            String clazzName = clazz.getName();
056            packageName = new String(clazzName.toCharArray(), 0, clazzName.lastIndexOf('.'));
057        }
058        return packageName;
059    }
060
061    /**
062     * Load the resource <code>resourceName</code>.
063     *
064     * Try each classloader in succession,
065     * until first succeeds, or all fail.
066     * If all fail and <code>resouceName</code> is not absolute
067     * (doesn't start with '/' character), then retry with
068     * <code>packageName/resourceName</code> after changing all
069     * '.' to '/'.
070     *
071     * @param spi The SPI type
072     * @param resourceName The name of the resource to load.
073     * @param loaders the class loaders holder
074     * @return The discovered {@link Resource} instance
075     * @throws DiscoveryException if the class implementing
076     *            the SPI cannot be found, cannot be loaded and
077     *            instantiated, or if the resulting class does not implement
078     *            (or extend) the SPI
079     */
080    public static Resource getResource(Class<?> spi,
081                                       String resourceName,
082                                       ClassLoaders loaders) throws DiscoveryException {
083        DiscoverResources explorer = new DiscoverResources(loaders);
084        ResourceIterator resources = explorer.findResources(resourceName);
085
086        if (spi != null  &&
087            !resources.hasNext()  &&
088            resourceName.charAt(0) != '/') {
089            /**
090             * If we didn't find the resource, and if the resourceName
091             * isn't an 'absolute' path name, then qualify with
092             * package name of the spi.
093             */
094            resourceName = getPackageName(spi).replace('.','/') + "/" + resourceName;
095            resources = explorer.findResources(resourceName);
096        }
097
098        return resources.hasNext()
099               ? resources.nextResource()
100               : null;
101    }
102
103    /**
104     * Load named property file, optionally qualified by spi's package name
105     * as per Class.getResource.
106     *
107     * A property file is loaded using the following sequence of class loaders:
108     *   <ul>
109     *     <li>Thread Context Class Loader</li>
110     *     <li>DiscoverSingleton's Caller's Class Loader</li>
111     *     <li>SPI's Class Loader</li>
112     *     <li>DiscoverSingleton's (this class) Class Loader</li>
113     *     <li>System Class Loader</li>
114     *   </ul>
115     *
116     * @param spi The SPI type
117     * @param propertiesFileName The property file name.
118     * @param classLoaders The class loaders holder
119     * @return The loaded named property file, in {@code Properties} format
120     * @throws DiscoveryException Thrown if the name of a class implementing
121     *            the SPI cannot be found, if the class cannot be loaded and
122     *            instantiated, or if the resulting class does not implement
123     *            (or extend) the SPI.
124     */
125    public static Properties loadProperties(Class<?> spi,
126                                            String propertiesFileName,
127                                            ClassLoaders classLoaders) throws DiscoveryException {
128        Properties properties = null;
129
130        if (propertiesFileName != null) {
131            try {
132                Resource resource = getResource(spi, propertiesFileName, classLoaders);
133                if (resource != null) {
134                    InputStream stream = resource.getResourceAsStream();
135
136                    if (stream != null) {
137                        properties = new Properties();
138                        try {
139                            properties.load(stream);
140                        } finally {
141                            stream.close();
142                        }
143                    }
144                }
145            } catch (IOException e) {
146                // ignore
147            } catch (SecurityException e) {
148                // ignore
149            }
150        }
151
152        return properties;
153    }
154
155}