/* Copyright 2005-2006 Tim Fennell
*
* 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 net.sourceforge.stripes.config;
import java.lang.reflect.Modifier;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.servlet.FilterConfig;
import net.sourceforge.stripes.exception.StripesRuntimeException;
import net.sourceforge.stripes.util.Log;
import net.sourceforge.stripes.util.ReflectUtil;
import net.sourceforge.stripes.util.ResolverUtil;
import net.sourceforge.stripes.util.StringUtil;
import net.sourceforge.stripes.vfs.VFS;
/**
* <p>Resolves configuration properties that are used to bootstrap the system. Essentially this boils
* down to a handful of properties that are needed to figure out which configuration class should
* be instantiated, and any values needed by that configuration class to locate configuration
* information.</p>
*
* <p>Properties are looked for in the following order:
* <ul>
* <li>Initialization Parameters for the Dispatcher servlet</li>
* <li>Initialization Parameters for the Servlet Context</li>
* <li>Java System Properties</li>
* </ul>
* </p>
*
* @author Tim Fennell
*/
public class BootstrapPropertyResolver {
private static final Log log = Log.getInstance(BootstrapPropertyResolver.class);
private FilterConfig filterConfig;
/** The Configuration Key for looking up the comma separated list of VFS classes. */
public static final String VFS_CLASSES = "VFS.Classes";
/** The Configuration Key for looking up the comma separated list of extension packages. */
public static final String PACKAGES = "Extension.Packages";
/** Constructs a new BootstrapPropertyResolver with the given ServletConfig. */
public BootstrapPropertyResolver(FilterConfig filterConfig) {
setFilterConfig(filterConfig);
initVFS();
}
/** Stores a reference to the filter's FilterConfig object. */
public void setFilterConfig(FilterConfig filterConfig) {
this.filterConfig = filterConfig;
}
/** Returns a reference to the StripesFilter's FilterConfig object. */
public FilterConfig getFilterConfig() {
return this.filterConfig;
}
/** Add {@link VFS} implementations that are specified in the filter configuration. */
@SuppressWarnings("unchecked")
protected void initVFS() {
List<Class<?>> vfsImpls = getClassPropertyList(VFS_CLASSES);
for (Class<?> clazz : vfsImpls) {
if (!VFS.class.isAssignableFrom(clazz))
log.warn("Class ", clazz.getName(), " does not extend ", VFS.class.getName());
else
VFS.addImplClass((Class<? extends VFS>) clazz);
}
}
/**
* Fetches a configuration property in the manner described in the class level javadoc for
* this class.
*
* @param key the String name of the configuration value to be looked up
* @return String the value of the configuration item or null
*/
public String getProperty(String key) {
String value = null;
try {
value = this.filterConfig.getInitParameter(key);
}
catch (AccessControlException e) {
log.debug("Security manager prevented " + getClass().getName()
+ " from reading filter init-param" + key);
}
if (value == null) {
try {
value = this.filterConfig.getServletContext().getInitParameter(key);
}
catch (AccessControlException e) {
log.debug("Security manager prevented " + getClass().getName()
+ " from reading servlet context init-param" + key);
}
}
if (value == null) {
try {
value = System.getProperty(key);
}
catch (AccessControlException e) {
log.debug("Security manager prevented " + getClass().getName()
+ " from reading system property " + key);
}
}
return value;
}
/**
* Attempts to find a class the user has specified in web.xml or by auto-discovery in packages
* listed in web.xml under Extension.Packages. Classes specified in web.xml take precedence.
*
* @param paramName the parameter to look for in web.xml
* @param targetType the type that we're looking for
* @return the Class that was found
*/
@SuppressWarnings("unchecked")
public <T> Class<? extends T> getClassProperty(String paramName, Class<T> targetType)
{
Class<? extends T> clazz = null;
String className = getProperty(paramName);
if (className != null) {
// web.xml takes precedence
try {
clazz = ReflectUtil.findClass(className);
log.info("Class implementing/extending ", targetType.getSimpleName(),
" found in web.xml: ", className);
}
catch (ClassNotFoundException e) {
log.error("Couldn't find class specified in web.xml under param ", paramName, ": ",
className);
}
}
else {
// we didn't find it in web.xml so now we check any extension packages
ResolverUtil<T> resolver = new ResolverUtil<T>();
String[] packages = StringUtil.standardSplit(getProperty(PACKAGES));
resolver.findImplementations(targetType, packages);
Set<Class<? extends T>> classes = resolver.getClasses();
removeDontAutoloadClasses(classes);
removeAbstractClasses(classes);
if (classes.size() == 1) {
clazz = classes.iterator().next();
className = clazz.getName();
log.info("Class implementing/extending ", targetType.getSimpleName(),
" found via auto-discovery: ", className);
}
else if (classes.size() > 1) {
throw new StripesRuntimeException(StringUtil.combineParts(
"Found too many classes implementing/extending ", targetType
.getSimpleName(), ": ", classes));
}
}
return clazz;
}
/**
* Attempts to find all classes the user has specified in web.xml.
*
* @param paramName the parameter to look for in web.xml
* @return a List of classes found
*/
public List<Class<?>> getClassPropertyList(String paramName)
{
List<Class<?>> classes = new ArrayList<Class<?>>();
String classList = getProperty(paramName);
if (classList != null) {
String[] classNames = StringUtil.standardSplit(classList);
for (String className : classNames) {
className = className.trim();
try {
classes.add(ReflectUtil.findClass(className));
}
catch (ClassNotFoundException e) {
throw new StripesRuntimeException("Could not find class [" + className
+ "] specified by the configuration parameter [" + paramName
+ "]. This value must contain fully qualified class names separated "
+ " by commas.");
}
}
}
return classes;
}
/**
* Attempts to find classes by auto-discovery in packages listed in web.xml under
* Extension.Packages.
*
* @param targetType the type that we're looking for
* @return a List of classes found
*/
public <T> List<Class<? extends T>> getClassPropertyList(Class<T> targetType)
{
ResolverUtil<T> resolver = new ResolverUtil<T>();
String[] packages = StringUtil.standardSplit(getProperty(PACKAGES));
resolver.findImplementations(targetType, packages);
Set<Class<? extends T>> classes = resolver.getClasses();
removeDontAutoloadClasses(classes);
removeAbstractClasses(classes);
return new ArrayList<Class<? extends T>>(classes);
}
/**
* Attempts to find all matching classes the user has specified in web.xml or by auto-discovery
* in packages listed in web.xml under Extension.Packages.
*
* @param paramName the parameter to look for in web.xml
* @param targetType the type that we're looking for
* @return the Class that was found
*/
@SuppressWarnings("unchecked")
public <T> List<Class<? extends T>> getClassPropertyList(String paramName, Class<T> targetType)
{
List<Class<? extends T>> classes = new ArrayList<Class<? extends T>>();
for (Class<?> clazz : getClassPropertyList(paramName)) {
// can't use addAll :(
classes.add((Class<? extends T>) clazz);
}
classes.addAll(getClassPropertyList(targetType));
return classes;
}
/** Removes any classes from the collection that are marked with {@link DontAutoLoad}. */
protected <T> void removeDontAutoloadClasses(Collection<Class<? extends T>> classes) {
Iterator<Class<? extends T>> iterator = classes.iterator();
while (iterator.hasNext()) {
Class<? extends T> clazz = iterator.next();
if (clazz.isAnnotationPresent(DontAutoLoad.class)) {
log.debug("Ignoring ", clazz, " because @DontAutoLoad is present.");
iterator.remove();
}
}
}
/** Removes any classes from the collection that are abstract or interfaces. */
protected <T> void removeAbstractClasses(Collection<Class<? extends T>> classes) {
Iterator<Class<? extends T>> iterator = classes.iterator();
while (iterator.hasNext()) {
Class<? extends T> clazz = iterator.next();
if (clazz.isInterface()) {
log.trace("Ignoring ", clazz, " because it is an interface.");
iterator.remove();
}
else if ((clazz.getModifiers() & Modifier.ABSTRACT) == Modifier.ABSTRACT) {
log.trace("Ignoring ", clazz, " because it is abstract.");
iterator.remove();
}
}
}
}