blob: b0b18fb251fac867dea84aba2370ea97bd682c18 [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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.apache.wiki.util;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.wiki.WikiEngine;
import org.apache.wiki.WikiProvider;
import org.apache.wiki.api.engine.PluginManager;
import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
import org.apache.wiki.api.exceptions.WikiException;
import org.apache.wiki.modules.InternalModule;
import org.jdom2.Element;
import org.picocontainer.DefaultPicoContainer;
import org.picocontainer.MutablePicoContainer;
import org.picocontainer.PicoContainer;
/**
* Contains useful utilities for class file manipulation. This is a static class,
* so there is no need to instantiate it.
*
* @since 2.1.29.
*/
public final class ClassUtil {
private static final Logger log = Logger.getLogger(ClassUtil.class);
/**
* The location of the classmappings.xml document. It will be searched for
* in the classpath. It's value is "{@value}".
*/
public static final String MAPPINGS = "ini/classmappings.xml";
private static MutablePicoContainer picoContainer = new DefaultPicoContainer();
private static boolean classLoaderSetup = false;
private static ClassLoader loader = null;
/**
* Initialize the class mappings document.
*/
static {
List< Element > nodes = XmlUtil.parse( MAPPINGS, "/classmappings/mapping" );
if( nodes.size() > 0 ) {
for( Iterator< Element > i = nodes.iterator(); i.hasNext(); ) {
Element f = i.next();
String key = f.getChildText("requestedClass");
String className = f.getChildText("mappedClass");
try {
picoContainer.addComponent(Class.forName(className));
} catch (ClassNotFoundException e) {
log.fatal(e,e);
}
log.debug("Mapped class '"+key+"' to class '"+className+"'");
}
} else {
log.info("Didn't find class mapping document in "+MAPPINGS);
}
}
/**
* Private constructor to prevent direct instantiation.
*/
private ClassUtil() {}
/**
* Attempts to find a class from a collection of packages. This will first
* attempt to find the class based on just the className parameter, but
* should that fail, will iterate through the "packages" -list, prefixes
* the package name to the className, and then tries to find the class
* again.
*
* @param packages A List of Strings, containing different package names.
* @param className The name of the class to find.
* @return The class, if it was found.
* @throws ClassNotFoundException if this particular class cannot be found
* from the list.
*/
public static Class<?> findClass( List< String > packages, List< String > externaljars, String className ) throws ClassNotFoundException {
if (!classLoaderSetup) {
loader = setupClassLoader(externaljars);
}
try {
return loader.loadClass( className );
} catch( ClassNotFoundException e ) {
for( Iterator< String > i = packages.iterator(); i.hasNext(); ) {
String packageName = i.next();
try {
return loader.loadClass( packageName + "." + className );
} catch( ClassNotFoundException ex ) {
// This is okay, we go to the next package.
}
}
}
throw new ClassNotFoundException( "Class '" + className + "' not found in search path!" );
}
/**
* Setup the plugin classloader.
* Check if there are external JARS to add via property {@link org.apache.wiki.api.engine.PluginManager#PROP_EXTERNALJARS}
*
* @return the classloader that can load classes from the configured external jars or
* ,if not specified, the classloader that loaded this class.
* @param externaljars
*/
private static ClassLoader setupClassLoader(List<String> externaljars) {
classLoaderSetup = true;
log.info("setting up classloaders for external (plugin) jars");
if (externaljars.size() == 0) {
log.info("no external jars configured, using standard classloading");
return ClassUtil.class.getClassLoader();
}
URL[] urls = new URL[externaljars.size()];
int i = 0;
try {
for (String externaljar : externaljars) {
File jarFile = new File(externaljar);
URL ucl = jarFile.toURI().toURL();
urls[i++] = ucl;
log.info("added " + ucl + " to list of external jars");
}
} catch (MalformedURLException e) {
log.error("exception while setting up classloaders for external jars via property" + PluginManager.PROP_EXTERNALJARS + ", continuing without external jars.");
return ClassUtil.class.getClassLoader();
}
return new URLClassLoader(urls, ClassUtil.class.getClassLoader());
}
/**
*
* It will first attempt to instantiate the class directly from the className,
* and will then try to prefix it with the packageName.
*
* @param packageName A package name (such as "org.apache.wiki.plugins").
* @param className The class name to find.
* @return The class, if it was found.
* @throws ClassNotFoundException if this particular class cannot be found.
*/
public static Class<?> findClass(String packageName, String className) throws ClassNotFoundException {
try {
return ClassUtil.class.getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
return ClassUtil.class.getClassLoader().loadClass(packageName + "." + className);
}
}
/**
* Lists all the files in classpath under a given package.
*
* @param rootPackage the base package. Can be {code null}.
* @return all files entries in classpath under the given package
*/
public static List< String > classpathEntriesUnder( final String rootPackage )
{
List< String > results = new ArrayList< String >();
Enumeration< URL > en = null;
if( StringUtils.isNotEmpty( rootPackage ) ) {
try
{
en = ClassUtil.class.getClassLoader().getResources( rootPackage );
}
catch( IOException e )
{
log.error( e.getMessage(), e );
}
}
while( en != null && en.hasMoreElements() )
{
URL url = en.nextElement();
try
{
if( "jar".equals( url.getProtocol() ) )
{
jarEntriesUnder( results, ( JarURLConnection )url.openConnection(), rootPackage );
}
else if( "file".equals( url.getProtocol() ) )
{
fileEntriesUnder( results, new File( url.getFile() ), rootPackage );
}
}
catch (IOException ioe)
{
log.error( ioe.getMessage(), ioe );
}
}
return results;
}
/**
* Searchs for all the files in classpath under a given package, for a given {@link File}. If the
* {@link File} is a directory all files inside it are stored, otherwise the {@link File} itself is
* stored
*
* @param results collection in which the found entries are stored
* @param file given {@link File} to search in.
* @param rootPackage base package.
*/
static void fileEntriesUnder( List< String > results, File file, String rootPackage )
{
log.debug( "scanning [" + file.getName() +"]" );
if( file.isDirectory() ) {
Iterator< File > files = FileUtils.iterateFiles( file, null, true );
while( files.hasNext() )
{
File subfile = files.next();
// store an entry similar to the jarSearch(..) below ones
String entry = StringUtils.replace( subfile.getAbsolutePath(), file.getAbsolutePath() + File.separatorChar, StringUtils.EMPTY );
results.add( rootPackage + "/" + entry );
}
} else {
results.add( file.getName() );
}
}
/**
* Searchs for all the files in classpath under a given package, for a given {@link JarURLConnection}.
*
* @param results collection in which the found entries are stored
* @param jurlcon given {@link JarURLConnection} to search in.
* @param rootPackage base package.
*/
static void jarEntriesUnder( List< String > results, JarURLConnection jurlcon, String rootPackage )
{
JarFile jar = null;
try
{
jar = jurlcon.getJarFile();
log.debug( "scanning [" + jar.getName() +"]" );
Enumeration< JarEntry > entries = jar.entries();
while( entries.hasMoreElements() )
{
JarEntry entry = entries.nextElement();
if( entry.getName().startsWith( rootPackage ) && !entry.isDirectory() )
{
results.add( entry.getName() );
}
}
}
catch( IOException ioe )
{
log.error( ioe.getMessage(), ioe );
}
finally
{
if (jar != null)
{
try
{
jar.close();
}
catch( IOException ioe )
{
log.error( ioe.getMessage(), ioe );
}
}
}
}
public static <T extends InternalModule> T getInternalModule( Class<T> classType, WikiEngine engine, Properties props ) throws WikiException {
InternalModule module = (InternalModule) getPicoContainer().getComponent(classType);
module.initialize(engine, props);
return classType.cast(module);
}
public static <T extends WikiProvider> T getWikiProvider( Class<T> classType, WikiEngine engine, Properties props, String packageName, String providerClassName, WikiProvider defaultProvider, boolean throwException) throws WikiException {
WikiProvider provider = defaultProvider;
String defaultProviderClassName = defaultProvider != null ? defaultProvider.getClass().getName() : null;
String errorMessageLoad = "Failed loading WikiProvider "+providerClassName+", will use "+defaultProviderClassName+". ";
try {
Class<?> providerClass = ClassUtil.findClass(packageName, providerClassName);
provider = (WikiProvider) providerClass.newInstance();
} catch (ClassNotFoundException e) {
log.warn("no class for WikiProvider "+providerClassName+" "+e.getMessage(), e);
if (throwException) { throw new WikiException(errorMessageLoad+e.getMessage()); }
} catch (InstantiationException e) {
log.warn("Faulty class WikiProvider "+providerClassName+" "+e.getMessage(), e);
if (throwException) { throw new WikiException(errorMessageLoad+e.getMessage()); }
} catch (IllegalAccessException e) {
log.warn("Illegal class WikiProvider "+providerClassName+" "+e.getMessage(), e);
if (throwException) { throw new WikiException(errorMessageLoad+e.getMessage()); }
}
if (null == provider) {
provider = defaultProvider;
} else {
String errorMessageInit = "Failed initializing WikiProvider "+providerClassName+", will use "+defaultProviderClassName+". ";
try {
provider.initialize(engine, props);
} catch (NoRequiredPropertyException e) {
log.warn(errorMessageInit+e.getMessage(), e);
if (throwException) { throw new WikiException(errorMessageInit+e.getMessage()); }
provider = defaultProvider;
} catch (WikiException e) {
log.warn(errorMessageInit+e.getMessage(), e);
if (throwException) { throw new WikiException(errorMessageInit+e.getMessage()); }
provider = defaultProvider;
}
}
return classType.cast(provider);
}
/**
* This method is used to locate and instantiate a mapped class.
* You may redefine anything in the resource file which is located in your classpath
* under the name <code>ClassUtil.MAPPINGS ({@value #MAPPINGS})</code>.
* <p>
* This is an extremely powerful system, which allows you to remap many of
* the JSPWiki core classes to your own class. Please read the documentation
* included in the default <code>{@value #MAPPINGS}</code> file to see
* how this method works.
*
* @param requestedClass The name of the class you wish to instantiate.
* @return An instantiated Object.
* @throws WikiException If the class cannot be found or instantiated.
* @since 2.5.40
*/
public static Object getMappedObject( String requestedClass )
throws WikiException
{
// WikiEngine engine = WikiEngine.getInstance(context,null);
// Properties props = engine.getWikiProperties();
return getMappedObject(requestedClass, null, null );
}
/**
* This method is used to locate and instantiate a mapped class.
* You may redefine anything in the resource file which is located in your classpath
* under the name <code>{@value #MAPPINGS}</code>.
* <p>
* This is an extremely powerful system, which allows you to remap many of
* the JSPWiki core classes to your own class. Please read the documentation
* included in the default <code>{@value #MAPPINGS}</code> file to see
* how this method works.
* <p>
* This method takes in an object array for the constructor arguments for classes
* which have more than two constructors.
*
* @param requestedClass The name of the class you wish to instantiate.
* @param initargs The parameters to be passed to the constructor. May be <code>null</code>.
* @return An instantiated Object.
* @throws WikiException If the class cannot be found or instantiated. The error is logged.
* @since 2.5.40
*/
public static Object getMappedObject( String requestedClass, WikiEngine engine, Properties props )
throws WikiException
{
try {
Object o = ClassUtil.getPicoContainer().getComponent(Class.forName(requestedClass));
if (engine != null && props != null && o instanceof InternalModule) {
((InternalModule)o).initialize(engine, props);
}
return o;
} catch (ClassNotFoundException e) {
throw new WikiException(e.getMessage());
}
}
public static PicoContainer getPicoContainer() {
return picoContainer;
}
}