/*
* Copyright (C) 2009 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.container.configuration;
import org.exoplatform.commons.utils.SecurityHelper;
import org.exoplatform.container.xml.Component;
import org.exoplatform.container.xml.Configuration;
import org.exoplatform.container.xml.Deserializer;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletContext;
/**
* Jul 19, 2004
*
* @author: Tuan Nguyen
* @email: tuan08@users.sourceforge.net
* @version: $Id: ConfigurationServiceImpl.java,v 1.8 2004/10/30 02:29:51 tuan08
* Exp $
*/
public class ConfigurationManagerImpl implements ConfigurationManager
{
final static public String WAR_CONF_LOCATION = "/WEB-INF";
private static final Log LOG = ExoLogger.getLogger("exo.kernel.container.ConfigurationManagerImpl");
protected Configuration configurations_;
private ServletContext scontext_;
private ClassLoader scontextClassLoader_;
private String contextPath;
private boolean validateSchema = true;
private boolean logEnabled = true;
/** . */
private final Set<String> profiles;
/** The URL of the current document being unmarshalled. */
private static final ThreadLocal<URL> currentURL = new ThreadLocal<URL>();
/**
* Returns the URL of the current document being unmarshalled or null.
* @return the URL
*/
public static URL getCurrentURL()
{
return currentURL.get();
}
public ConfigurationManagerImpl()
{
this.profiles = Collections.emptySet();
}
public ConfigurationManagerImpl(Set<String> profiles)
{
this.profiles = profiles;
}
public ConfigurationManagerImpl(Set<String> profiles, boolean logEnabled)
{
this(profiles);
this.logEnabled = logEnabled;
}
public ConfigurationManagerImpl(ServletContext context, Set<String> profiles)
{
scontext_ = context;
this.profiles = profiles;
}
public ConfigurationManagerImpl(ClassLoader loader, Set<String> profiles)
{
scontextClassLoader_ = loader;
this.profiles = profiles;
}
public Configuration getConfiguration()
{
return configurations_;
}
public void addConfiguration(ServletContext context, String url) throws Exception
{
if (url == null)
return;
addConfiguration(context, getURL(context, url));
}
public void addConfiguration(String url) throws Exception
{
if (url == null)
return;
addConfiguration(getURL(url));
}
public void addConfiguration(Collection<URL> urls)
{
Iterator<URL> i = urls.iterator();
while (i.hasNext())
{
URL url = i.next();
addConfiguration(url);
}
}
public void addConfiguration(URL url)
{
addConfiguration(scontext_, url);
}
private void addConfiguration(ServletContext context, URL url)
{
if (logEnabled && LOG_DEBUG)
LOG.info("Add configuration " + url);
if (url == null)
return;
try
{
contextPath = (new File(url.toString())).getParent() + "/";
contextPath = contextPath.replaceAll("\\\\", "/");
}
catch (Exception e)
{
contextPath = null;
}
// Just to prevent some nasty bug to happen
if (currentURL.get() != null)
{
throw new IllegalStateException("Would not expect that");
}
else
{
currentURL.set(url);
}
//
try
{
ConfigurationUnmarshaller unmarshaller = new ConfigurationUnmarshaller(profiles);
Configuration conf = unmarshaller.unmarshall(url);
if (configurations_ == null)
configurations_ = conf;
else
configurations_.mergeConfiguration(conf);
importConf(unmarshaller, conf);
}
catch (Exception ex)
{
LOG.error("Cannot process the configuration " + currentURL.get(), ex);
}
finally
{
currentURL.set(null);
}
}
/**
* Recursively import the configuration files
*
* @param unmarshaller the unmarshaller used to unmarshall the configuration file to import
* @param conf the configuration in which we get the list of files to import
* @throws Exception if an exception occurs while loading the files to import
*/
private void importConf(ConfigurationUnmarshaller unmarshaller, Configuration conf) throws Exception
{
importConf(unmarshaller, conf, 1);
}
/**
* Recursively import the configuration files
*
* @param unmarshaller the unmarshaller used to unmarshall the configuration file to import
* @param conf the configuration in which we get the list of files to import
* @param depth used to log properly the URL of the file to import
* @throws Exception if an exception occurs while loading the files to import
*/
private void importConf(ConfigurationUnmarshaller unmarshaller, Configuration conf, int depth) throws Exception
{
List<String> urls = conf.getImports();
if (urls != null)
{
StringBuilder prefix = new StringBuilder(depth);
for (int i = 0; i < depth; i++)
{
prefix.append('\t');
}
for (int i = 0; i < urls.size(); i++)
{
String uri = (String)urls.get(i);
URL urlObject = getURL(uri);
if (urlObject != null)
{
if (logEnabled && LOG_DEBUG)
LOG.info(prefix + "import " + urlObject);
// Set the URL of imported file
currentURL.set(urlObject);
conf = unmarshaller.unmarshall(urlObject);
configurations_.mergeConfiguration(conf);
importConf(unmarshaller, conf, depth + 1);
}
else
{
LOG.warn("Couldn't process the URL for " + uri + " configuration file ignored ");
}
}
}
}
public void processRemoveConfiguration()
{
if (configurations_ == null)
return;
List<String> list = configurations_.getRemoveConfiguration();
if (list != null)
{
for (int i = 0; i < list.size(); i++)
{
String type = (String)list.get(i);
configurations_.removeConfiguration(type);
}
}
}
public Component getComponent(String service)
{
return configurations_.getComponent(service);
}
public Component getComponent(Class<?> clazz)
{
return configurations_.getComponent(clazz.getName());
}
public Collection<Component> getComponents()
{
if (configurations_ == null)
return null;
return configurations_.getComponents();
}
public boolean isValidateSchema()
{
return validateSchema;
}
public void setValidateSchema(boolean validateSchema)
{
this.validateSchema = validateSchema;
}
public URL getResource(String url, String defaultURL) throws Exception
{
if (url == null)
url = defaultURL;
return getResource(url);
}
public URL getResource(String uri) throws Exception
{
return getURL(uri);
}
public InputStream getInputStream(String url, String defaultURL) throws Exception
{
if (url == null)
url = defaultURL;
return getInputStream(url);
}
public InputStream getInputStream(String uri) throws Exception
{
final URL url = getURL(uri);
if (url == null)
{
throw new IOException("Resource (" + uri
+ ") could not be found or the invoker doesn't have adequate privileges to get the resource");
}
return SecurityHelper.doPrivilegedIOExceptionAction(new PrivilegedExceptionAction<InputStream>()
{
public InputStream run() throws Exception
{
return url.openStream();
}
});
}
public URL getURL(String url) throws Exception
{
return getURL(scontext_, url);
}
private URL getURL(final ServletContext context, String url) throws Exception
{
if (url == null)
{
return null;
}
else if (url.startsWith("jar:"))
{
final String path = removePrefix("jar:/", url);
final ClassLoader cl = Thread.currentThread().getContextClassLoader();
return SecurityHelper.doPrivilegedAction(new PrivilegedAction<URL>()
{
public URL run()
{
return cl.getResource(path);
}
});
}
else if (url.startsWith("classpath:"))
{
final String path = removePrefix("classpath:/", url);
final ClassLoader cl = Thread.currentThread().getContextClassLoader();
return SecurityHelper.doPrivilegedAction(new PrivilegedAction<URL>()
{
public URL run()
{
return cl.getResource(path);
}
});
}
else if (url.startsWith("war:"))
{
String path = removePrefix("war:", url);
if (context != null)
{
final String fPath = path;
return SecurityHelper.doPrivilegedMalformedURLExceptionAction(new PrivilegedExceptionAction<URL>()
{
public URL run() throws Exception
{
return context.getResource(WAR_CONF_LOCATION + fPath);
}
});
}
if (scontextClassLoader_ != null)
{
if (path.startsWith("/"))
{
// The ClassLoader doesn't support the first "/"
path = path.substring(1);
}
final String fPath = path;
return SecurityHelper.doPrivilegedAction(new PrivilegedAction<URL>()
{
public URL run()
{
return scontextClassLoader_.getResource(fPath);
}
});
}
throw new Exception("unsupport war uri in this configuration service");
}
else if (url.startsWith("file:"))
{
url = resolveFileURL(url);
return new URL(url);
}
else if (url.indexOf(":") < 0 && contextPath != null)
{
return new URL(contextPath + url.replace('\\', '/'));
}
return null;
}
/**
* This methods is used to convert the given into a valid url, it will:
* <ol>
* <li>Resolve variables in the path if they exist</li>
* <li>Replace windows path separators with proper separators</li>
* <li>Ensure that the path start with file:///</li>
* </ol>
* , then it will
* @param url the url to resolve
* @return the resolved url
*/
private String resolveFileURL(String url)
{
url = Deserializer.resolveVariables(url);
// we ensure that we don't have windows path separator in the url
url = url.replace('\\', '/');
if (!url.startsWith("file:///"))
{
// The url is invalid, so we will fix it
// it happens when we use a path of type file://${path}, under
// linux or mac os the path will start with a '/' so the url
// will be correct but under windows we will have something
// like C:\ so the first '/' is missing
if (url.startsWith("file://"))
{
// The url is of type file://, so one '/' is missing
url = "file:///" + url.substring(7);
}
else if (url.startsWith("file:/"))
{
// The url is of type file:/, so two '/' are missing
url = "file:///" + url.substring(6);
}
else
{
// The url is of type file:, so three '/' are missing
url = "file:///" + url.substring(5);
}
}
return url;
}
protected String removePrefix(String prefix, String url)
{
return url.substring(prefix.length(), url.length());
}
}