All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openbp.common.classloader.XClassLoaderBase Maven / Gradle / Ivy

The newest version!
/*
 *   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.openbp.common.classloader;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.openbp.common.MsgFormat;
import org.openbp.common.logger.LogLevel;
import org.openbp.common.string.StringUtil;

/**
 * This class implements a sophisticated classloader that supports
 * loading from classes from various repositories (which might be
 * \.jar, \.zip-files or directories) and a caching mechanism for
 * loaded classes.
 *
 * To avoid multi-threading problems, this class loader is immutable after having
 * been constructed. This means, all configuration data necessary is passed as
 * argument to the constructor, and changing the configuration object
 * (see {@link XClassLoaderConfiguration}) after being passed to the constructor
 * doesn't influence the class loader any more (which is achieved by cloning the
 * configuration). Consequentially, the configuration can and should be reused.
 *
 * In order to support a broad field of applications for this class,
 * it is kept free of specific logging code, instead, own logging levels together
 * with an abstract logging method are used to allow easy integration of
 * the appropriate logging mechanism.
 *
 * Implementation note: Actually, the class loader wouldn't have to extend URLClassLoader.
 * This is done only to make the Tomcat Jasper JSP engine happy, which hard-wiredly expects an URLClassLoader.
 *
 * @author Heiko Erhardt
 */
public abstract class XClassLoaderBase extends URLClassLoader
{
	/** Class loading time debug flag */
	public static boolean DEBUG_LOAD_TIME = false;

	private static URL [] noUrls = new URL [0];

	/** A clone of the configuration passed during construction of the class loader. */
	protected XClassLoaderConfiguration configuration;

	/** The class cache (maps class names to Class objects). */
	private Hashtable classCache;

	/**
	 * Table mapping resource names (i\.e\. class names) to {@link ResourceEntry} objects (which may specify a file-
	 * or zip-based resource).
	 */
	private Map entries;

	/** The time spent while loading classes. */
	private long loadClassTime;

	/**
	 * Constructor.
	 *
	 * @param configuration The configuration to be used for this class loader. Must not be null.
	 * @throws Exception On any error that occurs while scanning the repositories specified in the class loader configuration
	 */
	public XClassLoaderBase(XClassLoaderConfiguration configuration)
		throws Exception
	{
		super(noUrls);
		init(configuration);
	}

	/**
	 * Protected constructor. Should only be used by subclasses that need to take essential
	 * actions before starting with the configuration processing. If this constructor was used,
	 * it is absolute necessary to call {@link #init}!
	 */
	protected XClassLoaderBase()
	{
		super(noUrls);
	}

	/**
	 * This method initializes the class loader with the configuration passed.
	 *
	 * @param configuration The configuration to be used for this class loader. Must not be null.
	 * @throws Exception On any error that occurs while scanning the repositories specified in the class loader configuration
	 */
	protected void init(XClassLoaderConfiguration configuration)
		throws Exception
	{
		// Construction without configuration is not possible.
		if (configuration == null)
		{
			throw new IllegalArgumentException("Creation of classloader without configuration is not supported.");
		}

		// Clone configuration to keep the classloader's configuration immutable.
		this.configuration = (XClassLoaderConfiguration) configuration.clone();

		// Initialize the logger
		setupLogger();

		if (isLogEnabled(LogLevel.INFO))
		{
			configuration.printDebug(this);
		}

		// Set up internal data structures.
		classCache = new Hashtable(1000);
		entries = new HashMap(1000);

		// Scan repositories for classes.
		scanRepositories();
	}

	/**
	 * Clears all information about classes in the path and rescans the specified class path. This
	 * method should be called if a class file was added to a directory of the class path
	 * programatically (e. g. by an invoked compilation or byte code construction).
	 * @throws Exception On any error that occurs while scanning the repositories specified in the class loader configuration
	 */
	public void rescan()
		throws Exception
	{
		// Clear lists of classes found.
		entries.clear();

		// Scan repositories again.
		scanRepositories();
	}

	/**
	 * This returns the parent class loader of this class loader
	 *
	 * @return The parent class loader or null if no explicit parent class loader has been specified when creating this class loader
	 */
	public ClassLoader getParentClassLoader()
	{
		return configuration.getParentClassLoader();
	}

	/**
	 * Gets the a clone of the configuration passed during construction of the class loader..
	 */
	public XClassLoaderConfiguration getConfiguration()
	{
		return configuration;
	}

	/**
	 * Gets a resource entry by its key.
	 *
	 * @param key Key
	 * @return The entry or null if no such entry exists
	 */
	public ResourceEntry getResourceEntry(String key)
	{
		return (ResourceEntry) entries.get(key);
	}

	/**
	 * Adds a resource entry to the class loader.
	 *
	 * @param key Key of the entry (e. g. a fully qualified class name)
	 * @param entry Entry to add
	 */
	public void addResourceEntry(String key, ResourceEntry entry)
	{
		entries.put(key, entry);
	}

	/**
	 * Adds the given resource entry as class mapping of the entry specifies a class file.
	 * Does nothing if the entry does not denote a class file.
	 *
	 * @param resourceName Resource name (relative path name or zip entry name)
	 * @param entry Entry to add
	 */
	protected void addResourceEntryForClass(String resourceName, ResourceEntry entry)
	{
		// For class files, store a mapping of the class name
		int index;
		if ((index = resourceName.indexOf(".class")) >= 0)
		{
			String className = resourceName.substring(0, index);
			className = className.replace('/', '.');
			className = className.replace('\\', '.');
			addResourceEntry(className, entry);
		}
	}

	//////////////////////////////////////////////////
	// @@ ClassLoader overrides
	//////////////////////////////////////////////////

	/**
	 * Overrides the loadClass method from the standard java class loader.
	 *
	 * @param className Name of the class to be loaded
	 * @param resolve If true, all dependents are loaded too
	 * @return The class object
	 * @throws ClassNotFoundException If the class was not found
	 */
	public Class loadClass(String className, boolean resolve)
		throws ClassNotFoundException
	{
		Class result = null;

		// Get current time.
		long time = 0;
		if (DEBUG_LOAD_TIME && isLogEnabled(LogLevel.DEBUG))
		{
			time = System.currentTimeMillis();
		}

		// Try to find class in our own repositories...
		Class cls = loadClassFromPath(className, resolve);

		// ...if found...
		if (cls != null)
		{
			// ...return it.
			result = cls;
		}
		else
		{
			// ...or give the System class loader a chance.
			// Note that this will throw a ClassNotFoundException on error!
			result = findSystemClass(className);
		}

		// Log profiling info.
		if (DEBUG_LOAD_TIME && isLogEnabled(LogLevel.DEBUG))
		{
			// Calculate time we've spent in the method.
			time = System.currentTimeMillis() - time;

			// Add to load class time.
			loadClassTime += time;

			log(LogLevel.DEBUG, "loadClass('" + className + "') took " + time + " ms " + "(accumulated: " + loadClassTime + " ms)");
		}

		// Return the result.
		return result;
	}

	/**
	 * Determines the URL of a resource with a given name.
	 * This method returns null if no resource with this name is found.
	 *
	 * @param name Name of the desired resource
	 * @return The URL or null
	 */
	public URL getResource(String name)
	{
		return getResourceFromPath(name);
	}

	/**
	 * Finds a resource with a given name.
	 * This method returns null if no
	 * resource with this name is found.
	 *
	 * @param name Name of the desired resource
	 * @return An input stream to the resource or null
	 */
	public InputStream getResourceAsStream(String name)
	{
		return loadResourceFromPath(name);
	}

	//////////////////////////////////////////////////
	// @@ Class loading
	//////////////////////////////////////////////////

	/**
	 * Loads a class from a directory or zip file in the class path of the custom class loader.
	 *
	 * @param className Fully qualified name of the class to be loaded
	 * @param resolve If true, classes referenced by the class should be loaded, too
	 * @return The loaded class object or null if the class could not be loaded
	 */
	protected synchronized Class loadClassFromPath(String className, boolean resolve)
	{
		// Synchronize in order to prevent cache access conflicts.
		// Note that we may not synchronize on classCache (results in deadlock with synchronized(this) in parent class)!

		// Search the class cache first.
		Class cls = loadClassFromCache(className);
		if (cls != null)
		{
			return cls;
		}

		// Determine, whether we should try to load the class using the standard mechanism first.
		boolean standardFirst = (configuration.getTryStandardClassLoaderFirst() || configuration.getStandardPackages().containsClass(className)) && !configuration.getNonStandardPackages().containsClass(className);

		// Depending on whether we should try the standard mechanism first...
		if (standardFirst)
		{
			// First: Try loading using standard mechanism.
			cls = tryStandardLoaders(className);
			if (cls != null)
				return cls;

			// Second: Try loading from our own repositories.
			cls = tryRepositories(className, resolve);
			if (cls != null)
				return cls;
		}
		else
		{
			// First: Try loading from our own repositories.
			cls = tryRepositories(className, resolve);
			if (cls != null)
				return cls;

			// Second: Try loading usings standard mechanism.
			cls = tryStandardLoaders(className);
			if (cls != null)
				return cls;
		}

		// Log that the class couldn't be found.
		log(LogLevel.DEBUG, "Unresolved class: {0}", className);

		return null;
	}

	/**
	 * This method tries to find the class given by the passed name in any of our repositories.
	 * On successful load, the class is added to the cache. However, synchronization on the class
	 * cache access shouldn't be necessary here, because this method is invoked only with acquired
	 * lock on the cache.
	 *
	 * @param className The name of the class to be found
	 * @param resolve If true, any classes referenced by the found class should also be loaded
	 * @return Class
	 */
	private Class tryRepositories(String className, boolean resolve)
	{
		Class result = null;

		// A lot of things may fail (mostly because of I/O).
		try
		{
			byte [] data = null;

			// Try to find a matching entry
			ResourceEntry entry = (ResourceEntry) entries.get(className);

			if (entry != null)
			{
				data = entry.getBytes();

				if (data != null)
				{
					log(LogLevel.DEBUG, "Class resolved by custom loader: {0} from repository {1}", className, entry.getRepositoryUrl().toString());

					// Create the class (The class name is fully qualified, so we can pass it to
					// createClass to save decoding the class).
					result = createClass(className, data, resolve);

					// ...and store it in the cache.
					addClassToCache(className, result);
				}
			}
			else
			{
				log(LogLevel.DEBUG, "Class not found in any repository: {0}", className);
			}
		}
		catch (Exception e)
		{
			log(LogLevel.ERROR, "Exception loading class: {0}", className, e);
		}

		// Return the resulting class (or null in case of problems).
		return result;
	}

	/**
	 * Tries to load the class from the standard class loaders. As a side-effect, a successfully loaded
	 * class is put to the class cache.  However, synchronization on the class cache access shouldn't
	 * be necessary here, because this method is invoked only with acquired lock on the cache.
	 *
	 * @param className Fully qualified name of the class
	 * @return The class if it could be loaded or null
	 */
	private Class tryStandardLoaders(String className)
	{
		Class result = null;

		// We might run into ClassNotFoundException's using the standard class loaders.
		try
		{
			// Try the standard class loaders first.
			if (configuration.getParentClassLoader() != null)
			{
				result = configuration.getParentClassLoader().loadClass(className);
			}
			else
			{
				ClassLoader cl = getClass().getClassLoader();
				if (cl != null && cl != this)
				{
					result = cl.loadClass(className);
				}
				else
				{
					result = Class.forName(className);
				}
			}

			// If we got a result, put it into the class cache.
			if (result != null)
			{
				addClassToCache(className, result);

				log(LogLevel.DEBUG, "Class resolved by standard loader: {0}", className);
			}
		}
		catch (ClassNotFoundException e)
		{
		}

		// Return the result or null.
		return result;
	}

	//////////////////////////////////////////////////
	// @@ Class creation helper
	//////////////////////////////////////////////////

	/**
	 * Creates a class object from its binary data.
	 *
	 * @param className Name of the class
	 * @param data Class data (from file)
	 * @param resolve If true, all dependents are loaded too
	 * @return The class object
	 */
	public Class createClass(String className, byte [] data, boolean resolve)
	{
		Class clazz = defineClass(className, data, 0, data.length);
		if (resolve)
		{
			resolveClass(clazz);
		}
		return clazz;
	}

	//////////////////////////////////////////////////
	// @@ Resource management
	//////////////////////////////////////////////////

	/**
	 * Loads a resource from the class path.
	 *
	 * @param resourceName Name of the resource file
	 * @return An input stream to the resource or null if the resource was not found
	 */
	protected InputStream loadResourceFromPath(String resourceName)
	{
		log(LogLevel.DEBUG, "Loading resource: {0}", resourceName);

		// I/O operations might fail.
		try
		{
			byte [] data = null;

			// Try to find a matching entry
			String searchName = StringUtil.normalizePathName(resourceName);
			ResourceEntry entry = (ResourceEntry) entries.get(searchName);

			if (entry != null)
			{
				data = entry.getBytes();

				if (data != null)
				{
					log(LogLevel.DEBUG, "Resource resolved by custom loader: {0} from repository {1}", resourceName, entry.getRepositoryUrl().toString());

					// Return a stream based on the loaded data.
					return new ByteArrayInputStream(data);
				}
			}
		}
		catch (Exception e)
		{
			log(LogLevel.ERROR, "Exception loading resource: {0}", resourceName, e);
		}

		// If we have a parent loader, we should give it a chance to load the resource now.
		if (configuration.getParentClassLoader() != null)
		{
			InputStream stream = configuration.getParentClassLoader().getResourceAsStream(resourceName);
			if (stream != null)
			{
				log(LogLevel.DEBUG, "Resource resolved by standard loader: {0}", resourceName);
				return stream;
			}
		}

		// Log that we haven't found anything to return here.
		log(LogLevel.DEBUG, "Unresolved resource: {0}", resourceName);
		return null;
	}

	/**
	 * Loads a resource from the class path.
	 *
	 * @param resourceName Name of the resource file
	 * @return A URL of the resource or null if the resource could not be found
	 */
	protected URL getResourceFromPath(String resourceName)
	{
		log(LogLevel.DEBUG, "Getting resource: {0}", resourceName);

		// Try to find a matching entry
		String searchName = StringUtil.normalizePathName(resourceName);
		ResourceEntry entry = (ResourceEntry) entries.get(searchName);

		if (entry != null)
		{
			URL url = entry.createURL();
			log(LogLevel.DEBUG, "Resource URL resolved by custom loader: {0}", url.toString());
			return url;
		}

		// If we have no URL yet, give the parent class loader a chance...
		if (configuration.getParentClassLoader() != null)
		{
			URL url = configuration.getParentClassLoader().getResource(resourceName);
			if (url != null)
			{
				log(LogLevel.DEBUG, "Resource resolved by standard loader: {0}", resourceName);
				return url;
			}
		}

		// Log the we had no luck with the resource...
		log(LogLevel.DEBUG, "Unresolved resource: {0}", resourceName);
		return null;
	}

	//////////////////////////////////////////////////
	// @@ Class cache maintainance.
	//////////////////////////////////////////////////

	/**
	 * Adds a class to the class cache.
	 *
	 * @param className Fully qualified class name
	 * @param cls Class object
	 */
	protected void addClassToCache(String className, Class cls)
	{
		classCache.put(className, cls);
	}

	/**
	 * Tries to load the class from the cache.
	 *
	 * @param className Fully qualified name of the class
	 * @return The class if it is in the cache or null
	 */
	protected Class loadClassFromCache(String className)
	{
		return (Class) classCache.get(className);
	}

	//////////////////////////////////////////////////
	// @@ Repository management
	//////////////////////////////////////////////////

	/**
	 * This method scans all repositories in the configuration for classes.
	 * @throws Exception On any error that occurs while scanning the repositories specified in the class loader configuration
	 */
	protected void scanRepositories()
		throws Exception
	{
		long time = 0;
		if (DEBUG_LOAD_TIME && isLogEnabled(LogLevel.DEBUG))
		{
			time = System.currentTimeMillis();
		}

		List repositories = configuration.getRepositories();

		int n = repositories.size();
		for (int i = 0; i < n; i++)
		{
			String repositoryName = (String) repositories.get(i);
			File f = new File(repositoryName);
			try
			{
				String path = f.getAbsolutePath();

				if (f.isDirectory())
				{
					log(LogLevel.INFO, "Adding directory: {0}", path);

					scanDirectory(f, path);
				}
				else if (repositoryName.endsWith(".jar") || repositoryName.endsWith(".zip"))
				{
					scanJarFile(f);
				}
			}
			catch (Exception e)
			{
				log(LogLevel.ERROR, "Exception adding repository to class path: {0}", repositoryName, e);
			}
		}

		if (DEBUG_LOAD_TIME && isLogEnabled(LogLevel.DEBUG))
		{
			time = System.currentTimeMillis() - time;

			log(LogLevel.DEBUG, "scanRepositories took " + time + " ms.");
		}
	}

	/**
	 * This is a recursive method for mapping all of the files that are considered to be
	 * java classes into our entry table.
	 * By maintaining a list of all classes, we can greatly reduce the amount of time
	 * taken to retrieve the bytes for a specific class
	 * and can easily deduce when the required class is not available.
	 *
	 * @param dir Directory to map
	 * @param basePath Base directory, used when deducing which directories are package names
	 * @throws Exception On any error that occurs while scanning the repositories specified in the class loader configuration
	 */
	private void scanDirectory(File dir, String basePath)
		throws Exception
	{
		int basePathLen = basePath.length();

		String [] files = dir.list();
		for (int i = 0; i < files.length; i++)
		{
			File f = new File(dir.getPath(), files [i]);
			if (f.isDirectory())
			{
				// Recurse
				scanDirectory(f, basePath);
			}
			else
			{
				String path = f.getAbsolutePath();

				// Store the class file for access via getResource().
				String resourceName = path.substring(basePathLen + 1);
				resourceName = StringUtil.normalizePathName(resourceName);

				if (entries.get(resourceName) != null)
				{
					// This entry is already defined, ignore
					continue;
				}

				byte [] content = readContent(f);

				// Store the resource name of the zip entry in the table
				ResourceEntry entry = new ResourceEntry(f.toURL(), content);
				addResourceEntry(resourceName, entry);

				// For class files, store a mapping of the class name
				addResourceEntryForClass(resourceName, entry);
			}
		}
	}

	/**
	 * Reads the specified zip file, adding all of its contents that are java class files
	 * to the entry table.
	 *
	 * @param f Jar file to be mapped
	 * @throws Exception On any error that occurs while scanning the repositories specified in the class loader configuration
	 */
	private void scanJarFile(File f)
		throws Exception
	{
		String zipFileName = f.getAbsolutePath();
		log(LogLevel.INFO, "Adding zip file: {0}", zipFileName);

		FileInputStream fis = null;
		try
		{
			fis = new FileInputStream(zipFileName);
			scanJarFile(new BufferedInputStream(fis), f.toURL());
		}
		finally
		{
			if (fis != null)
			{
				fis.close();
			}
		}
	}

	/**
	 * Reads the zip file from the given input stream, adding all of its contents that are java class files
	 * to the entry table.
	 *
	 * @param in Input stream to the jar file
	 * @param jarFileUrl URL of the Jar file
	 * @throws Exception On any error that occurs while scanning the repositories specified in the class loader configuration
	 */
	private void scanJarFile(InputStream in, URL jarFileUrl)
		throws Exception
	{
		ZipInputStream zis = null;
		try
		{
			zis = new ZipInputStream(new BufferedInputStream(in));
			ZipEntry ze;

			// Read each entry from the ZipInputStream until no more entry found
			while ((ze = zis.getNextEntry()) != null)
			{
				String resourceName = ze.getName();
				if (entries.get(resourceName) != null)
				{
					// This entry is already defined, ignore
					continue;
				}

				int size = (int) ze.getSize();
				if (size == 0)
					continue;

				ByteArrayOutputStream bos = new ByteArrayOutputStream(4096);
				byte[] buffer = new byte[4096];
				int nRead;
				while ((nRead = zis.read(buffer)) > 0)
				{
					bos.write(buffer, 0, nRead);
				}
				byte [] content = bos.toByteArray();

				// Store the resource name of the zip entry in the table
				URL jarEntryUrl = buildJarEntryUrl(jarFileUrl, resourceName);
				ResourceEntry entry = new ResourceEntry(jarEntryUrl, content);
				addResourceEntry(resourceName, entry);

				// For class files, store a mapping of the class name
				addResourceEntryForClass(resourceName, entry);
			}
		}
		finally
		{
			if (zis != null)
			{
				zis.close();
			}
		}
	}

	/**
	 * Produces a jar URL from a given jar file and entry name.
	 *
	 * @param jarFileUrl The jar file
	 * @param entryName Name of the entry, e. g. "java/net/URL.class"
	 * @return A jar URL of the form jar:file:///path/to/jarfile.jar!/resourcename.
	 */
	private static URL buildJarEntryUrl(URL jarFileUrl, String entryName)
		throws MalformedURLException
	{
		// Append jar: protocol.
		StringBuffer result = new StringBuffer("jar:");

		// Append file URL to the archive file.
		result.append(jarFileUrl);

		// Append seperator.
		result.append("!/");

		// Adjust resource name and append it.
		result.append(entryName);

		// Return the result.
		return new URL(result.toString());
	}

	/**
	 * Reads the resource from the repository.
	 *
	 * @param file File to read
	 * @return Bytes that define the content of the entry (i. e. the class code)
	 * @throws Exception On any error that occurs while scanning the repositories specified in the class loader configuration
	 */
	public static byte [] readContent(File file)
		throws Exception
	{
		int size = (int) file.length();
		byte [] buffer = new byte [size];

		BufferedInputStream in = new BufferedInputStream(new FileInputStream(file), 4096);

		try
		{
			int pos = 0;
			while (pos < size)
			{
				pos += in.read(buffer, pos, size - pos);
			}
		}
		finally
		{
			in.close();
		}

		return buffer;
	}

	//////////////////////////////////////////////////
	// @@ Log support
	//////////////////////////////////////////////////

	/**
	 * Logs a message.
	 *
	 * @param logLevel Log level as defined by {@link LogLevel}
	 * @param msg The message to be logged
	 * @param e An exception that should be logged together with the message (might be null)
	 */
	protected void log(String logLevel, String msg, Exception e)
	{
		if (isLogEnabled(logLevel))
		{
			writeLog(logLevel, "(" + configuration.getName() + ") - " + msg, e);
		}
	}

	/**
	 * Logs a message.
	 *
	 * @param logLevel Log level as defined by {@link LogLevel}
	 * @param msg The message to be logged
	 * @param arg message argument
	 * @param e An exception that should be logged together with the message (might be null)
	 */
	protected void log(String logLevel, String msg, String arg, Exception e)
	{
		if (isLogEnabled(logLevel))
		{
			msg = MsgFormat.format(msg, arg);
			writeLog(logLevel, "(" + configuration.getName() + ") - " + msg, e);
		}
	}

	/**
	 * Logs a message.
	 *
	 * @param logLevel Log level as defined by {@link LogLevel}
	 * @param msg The message to be logged
	 * @param arg message argument
	 */
	protected void log(String logLevel, String msg, String arg)
	{
		if (isLogEnabled(logLevel))
		{
			msg = MsgFormat.format(msg, arg);
			writeLog(logLevel, "(" + configuration.getName() + ") - " + msg, null);
		}
	}

	/**
	 * Logs a message.
	 *
	 * @param logLevel Log level as defined by {@link LogLevel}
	 * @param msg The message to be logged
	 * @param arg message argument
	 * @param arg2 message argument
	 */
	protected void log(String logLevel, String msg, String arg, String arg2)
	{
		if (isLogEnabled(logLevel))
		{
			msg = MsgFormat.format(msg, arg, arg2);
			writeLog(logLevel, "(" + configuration.getName() + ") - " + msg, null);
		}
	}

	/**
	 * A convenience method that allows logging without an exception.
	 *
	 * @param logLevel Log level as defined by {@link LogLevel}
	 * @param msg The message to be logged
	 */
	protected void log(String logLevel, String msg)
	{
		if (isLogEnabled(logLevel))
		{
			writeLog(logLevel, "(" + configuration.getName() + ") - " + msg, null);
		}
	}

	/**
	 * Initializes the logger.
	 * This will be called before any log output is being written.
	 */
	protected abstract void setupLogger();

	/**
	 * Writes a message to the log.
	 * This abstract method has to be implemented by sub classes of this class to
	 * implement support of a custom log mechanism.
	 *
	 * @param logLevel Log level as defined by {@link LogLevel}
	 * @param msg Message to be logged
	 * @param e An exception that should be logged together with the message (might be null)
	 */
	protected abstract void writeLog(String logLevel, String msg, Exception e);

	/**
	 * Checks if the specified log level is enabled.
	 * This abstract method has to be implemented by sub classes of this class to
	 * implement support of a custom log mechanism.
	 *
	 * @param logLevel Log level as defined by {@link LogLevel}
	 * @return
	 *		true	The log level is enabled.
	 *		false	The log level is disabled.
	 */
	protected abstract boolean isLogEnabled(String logLevel);

	//////////////////////////////////////////////////
	// @@ Object overrides
	//////////////////////////////////////////////////

	/**
	 * Overridden toString method.
	 */
	public String toString()
	{
		StringBuffer buf = new StringBuffer();
		buf.append(getClass().getName());
		buf.append(" (");
		buf.append(configuration.getName());
		buf.append(") : ");
		buf.append(super.toString());
		return buf.toString();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy