org.springframework.extensions.config.source.UrlConfigSource.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.extensions.config.source.UrlConfigSource.java

Source

/**
 * Copyright (C) 2005-2009 Alfresco Software Limited.
 *
 * This file is part of the Spring Surf Extension project.
 *
 * 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.springframework.extensions.config.source;

import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import javax.servlet.ServletContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.extensions.config.ConfigException;
import org.springframework.util.ResourceUtils;
import org.springframework.web.context.ServletContextAware;

/**
 * ConfigSource that looks for a prefix to determine where to look for the config.</br>
 * Valid prefixes are:
 * <ul>
 *   <li><b>file:</b> the location provided is a path to a physical file</li>
 *   <li><b>classpath:</b> the location provided is a resource on the classpath</li>
 *   <li><b>http:</b> the location provided is a HTTP address</li>
 *   <li><b>jar:</b> the location provided is within a JAR file. The location can either
 *   be a single JAR file location or a wildcard in which case all JAR files on the classpath
 *   will be searched for the given entry. NOTE: Currently only searching in the META-INF
 *   folder is supported.</li>
 * </ul>
 * The default, if none of the above is detected, is <b>classpath</b>.  An example of
 * a URL is <code>file:/home/root/settings/config.xml</code>.
 * 
 * @author Derek Hulley
 * @author gavinc
 */
public class UrlConfigSource extends BaseConfigSource
        implements ServletContextAware, ApplicationContextAware, InitializingBean {
    public static final String PREFIX_JAR = "jar:";
    public static final String PREFIX_FILE = "file:";
    public static final String PREFIX_HTTP = "http:";
    public static final String PREFIX_CLASSPATH = "classpath:";
    public static final String PREFIX_CLASSPATH_ALL = "classpath*:";
    public static final String PREFIX_WEBAPP = "webapp:";

    private static final String WILDCARD = "*";
    private static final String META_INF = "META-INF";
    private static final String VFSJAR_URL_SEPARATOR = ".jar/";

    private static final Log logger = LogFactory.getLog(UrlConfigSource.class);

    private ServletContext servletContext;
    private ApplicationContext applicationContext;
    private List<String> sourceLocations = new ArrayList<String>();

    /* (non-Javadoc)
     * @see org.springframework.web.context.ServletContextAware#setServletContext(javax.servlet.ServletContext)
     */
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void afterPropertiesSet() throws Exception {
        for (String location : sourceLocations) {
            processSourceString(location);
        }
    }

    /**
     * Constructs a config location that figures out where to look for the config
     * 
     * @param sourceLocation The location from which to get config
     * 
     * @see ClassPathConfigSource#ClassPathConfigSource(java.util.List)
     */
    public UrlConfigSource(String sourceLocation) {
        this(sourceLocation, false);
    }

    /**
     * Constructs a config location that figures out where to look for the
     * config
     * 
     * @param sourceLocation
     *            The location from which to get config
     * @param init
     *            indicates whether sourceLocation should be processed directly
     *            in constructor
     * 
     * @see ClassPathConfigSource#ClassPathConfigSource(java.util.List)
     */
    public UrlConfigSource(String sourceLocation, boolean init) {
        if (init) {
            processSourceString(sourceLocation);
        } else {
            this.sourceLocations.add(sourceLocation);
        }
    }

    /**
     * Constructs a config location that figures out where to look for the config
     * 
     * @param sourceLocations List of locations from which to get the config
     */
    public UrlConfigSource(List<String> sourceLocations) {
        this(sourceLocations, false);
    }

    /**
     * Constructs a config location that figures out where to look for the
     * config
     * 
     * @param sourceLocations
     *            List of locations from which to get the config
     * @param init
     *            indicates whether sourceLocations should be processed directly
     *            in constructor
     */
    public UrlConfigSource(List<String> sourceLocations, boolean init) {
        if (init) {
            for (String location : sourceLocations) {
                processSourceString(location);
            }
        } else {
            this.sourceLocations.addAll(sourceLocations);
        }
    }

    /**
     * <p>A <code>BaseConfigSource</code> maintains a list of source strings which for a <code>UrlConfigSource</code> define
     * locations from which a configuration file can be retrieved. Each location should be prefixed by an identifier indicating
     * where to find the configuration file (e.g. on the classpath, as a physical file, etc). If a prefix is not provided
     * the the location is assumed to be on the classpath.</p>
     * <p>This method determines where to find the specified configuration file by matching the prefix to an implementation
     * of the <code>ConfigSource</code> interface and creating a new instance of that class using the location. The <code>ConfigSource</code>
     * interface requires that a <code>getInputStream</code> method is implemented and this is then invoked and the <code>InputStream</code>
     * returned by the <code>ConfigSource</code> is then returned by this method</p>
     *
     * @param sourceUrl A location from which to obtain an <code>InputStream</code> to a configuration file.
     * @return An <code>InputStream</code> to a configuration file.
     */
    public InputStream getInputStream(String sourceUrl) {
        // input stream
        InputStream inputStream = null;

        // determine the config source
        BaseConfigSource configSource = null;
        String sourceString = null;
        if (sourceUrl.startsWith(PREFIX_FILE)) {
            sourceString = sourceUrl.substring(5);
            configSource = new FileConfigSource(sourceString);
        } else if (sourceUrl.startsWith(PREFIX_HTTP)) {
            sourceString = sourceUrl;
            configSource = new HTTPConfigSource(sourceString);
        } else if (sourceUrl.startsWith(PREFIX_CLASSPATH)) {
            sourceString = sourceUrl.substring(10);
            configSource = new ClassPathConfigSource(sourceString);
        } else if (sourceUrl.startsWith(PREFIX_JAR)) {
            sourceString = sourceUrl;
            configSource = new JarConfigSource(sourceString);
        } else if (sourceUrl.startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
            sourceString = sourceUrl;

            InputStream is = null;
            try {
                URL resourceUrl = new URL(sourceString);
                URLConnection con = (URLConnection) resourceUrl.openConnection();
                is = con.getInputStream();
            } catch (MalformedURLException e) {
                if (logger.isDebugEnabled())
                    logger.debug("The malformed URL has occurred: " + sourceString, e);
                e.printStackTrace();
            } catch (IOException ioe) {
                if (logger.isDebugEnabled())
                    logger.debug("Failed to get input stream from open connection: " + sourceString, ioe);
            }

            // NOTE! special case for JBOSS VFS protocol! @see MNT-10050
            return is;
        } else if (sourceUrl.startsWith(PREFIX_WEBAPP)) {
            if (servletContext != null) {
                sourceString = sourceUrl.substring(PREFIX_WEBAPP.length());
                if (!sourceString.startsWith("/")) {
                    sourceString = "/" + sourceString;
                }

                try {
                    if (servletContext.getResource(sourceString) != null) {
                        sourceString = servletContext.getRealPath(sourceString);
                        if (sourceString != null) {
                            configSource = new FileConfigSource(sourceString);
                        }
                    }
                } catch (Exception ex) {
                    if (logger.isInfoEnabled())
                        logger.info("Unable to locate web application resource: " + sourceString);
                }
            }
        } else if (sourceUrl.indexOf(':') > -1) {
            throw new ConfigException("Config source cannot be determined: " + sourceUrl);
        } else {
            sourceString = sourceUrl;
            configSource = new ClassPathConfigSource(sourceString);
        }

        // retrieve input stream if we've identified a source
        if (sourceString != null && configSource != null) {
            inputStream = configSource.getInputStream(sourceString);
        }

        return inputStream;
    }

    /**
     * Processes the given source string and adds the resulting config
     * source files to the list to be parsed.
     * <p>
     * If the sourceString contains a wildcard the appropriate resolution
     * processing is performed to obtain a list of physical locations.
     * </p> 
     * 
     * @param sourceString
     */
    protected void processSourceString(String sourceString) {
        // support for wildcards on JAR file sources
        if (sourceString != null && sourceString.startsWith(PREFIX_JAR)
                && sourceString.indexOf(WILDCARD + JarConfigSource.JAR_PATH_SEPARATOR) != -1) {
            processWildcardJarSource(sourceString);
        } else {
            super.addSourceString(sourceString);
        }
    }

    /**
     * Processes the given JAR file pattern source. The classpath
     * will be searched for JAR files that contain files that match
     * the given pattern.
     * 
     * NOTE: Currently only files within the META-INF folder are supported
     * i.e. patterns that look like "jar:*!/META-INF/[filename]"
     * 
     * @param sourcePattern The wildcard pattern for files to find within JARs
     */
    protected void processWildcardJarSource(String sourcePattern) {
        String file = sourcePattern.substring(7);

        if (file.startsWith(META_INF) == false) {
            throw new UnsupportedOperationException(
                    "Only JAR file wildcard searches within the META-INF folder are currently supported");
        }

        try {
            if (applicationContext == null) {
                // get a list of all the JAR files that have the META-INF folder
                Enumeration<URL> urls = this.getClass().getClassLoader().getResources(META_INF);
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    // only add the item if is a reference to a JAR file
                    if (url.getProtocol().equals(JarConfigSource.JAR_PROTOCOL)) {
                        URLConnection conn = url.openConnection();
                        if (conn instanceof JarURLConnection) {
                            // open the jar file and see if it contains what we're looking for
                            JarURLConnection jarConn = (JarURLConnection) conn;
                            JarFile jar = ((JarURLConnection) conn).getJarFile();
                            ZipEntry entry = jar.getEntry(file);
                            if (entry != null) {
                                if (logger.isInfoEnabled())
                                    logger.info("Found " + file + " in " + jarConn.getJarFileURL());

                                String sourceString = JarConfigSource.JAR_PROTOCOL + ":"
                                        + jarConn.getJarFileURL().toExternalForm()
                                        + JarConfigSource.JAR_PATH_SEPARATOR + file;

                                super.addSourceString(sourceString);
                            } else if (logger.isDebugEnabled()) {
                                logger.debug("Did not find " + file + " in " + jarConn.getJarFileURL());
                            }
                        }
                    }
                }
            } else {
                Resource[] resources = applicationContext.getResources(PREFIX_CLASSPATH_ALL + file);

                for (Resource resource : resources) {
                    URL resourceUrl = resource.getURL();
                    if (ResourceUtils.isJarURL(resourceUrl)
                            || ResourceUtils.URL_PROTOCOL_VFSZIP.equals(resourceUrl.getProtocol())) {
                        URL jarURL = extractJarFileURL(resourceUrl);

                        String sourceString = JarConfigSource.JAR_PROTOCOL + ":" + jarURL.toString()
                                + JarConfigSource.JAR_PATH_SEPARATOR + file;

                        super.addSourceString(sourceString);
                    } else if (ResourceUtils.URL_PROTOCOL_VFS.equals(resourceUrl.getProtocol())) {
                        super.addSourceString(resourceUrl.toString());
                    }
                }
            }
        } catch (IOException ioe) {
            if (logger.isDebugEnabled())
                logger.debug("Failed to process JAR file wildcard: " + sourcePattern, ioe);
        }
    }

    private URL extractJarFileURL(final URL jarUrl) {
        String urlFile = jarUrl.getFile();
        int separatorIndex = urlFile.indexOf(JarConfigSource.JAR_PATH_SEPARATOR);
        if (separatorIndex == -1) {
            // support for JBoss VFS filesystem JAR files URLs
            urlFile = jarUrl.toString();
            separatorIndex = urlFile.indexOf(VFSJAR_URL_SEPARATOR);
            if (separatorIndex != -1) {
                // offset index to account for .jar suffix
                separatorIndex += 4;
            }
        }
        if (separatorIndex != -1) {
            String jarFile = urlFile.substring(0, separatorIndex);
            try {
                return new URL(jarFile);
            } catch (MalformedURLException ex) {
                // BEA WebLogic Server 12 fix, we know that there is jar separator and zip protocol
                if (jarUrl.getProtocol().equals(ResourceUtils.URL_PROTOCOL_ZIP)) {
                    try {
                        return new URL(ResourceUtils.FILE_URL_PREFIX + jarFile);
                    } catch (MalformedURLException mue) {
                        return jarUrl;
                    }
                } else {
                    return jarUrl;
                }
            }
        } else {
            return jarUrl;
        }
    }
}