org.acmsl.commons.regexpplugin.RegexpManager.java Source code

Java tutorial

Introduction

Here is the source code for org.acmsl.commons.regexpplugin.RegexpManager.java

Source

//;-*- mode: java -*-
/*
                    ACM-SL Commons
    
Copyright (C) 2002-today  Jose San Leandro Armendariz
                          chous@acm-sl.org
    
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
    
This library 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
General Public License for more details.
    
You should have received a copy of the GNU General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    
Thanks to ACM S.L. for distributing this library under the LGPL license.
Contact info: jose.sanleandro@acm-sl.com
    
 ******************************************************************************
 *
 * Filename: RegexpManager.java
 *
 * Author: Jose San Leandro Armendariz
 *
 * Description: Manages which regexp engine to use, acting as a facade
 *              hiding all details of building or retrieving implementations.
 *
 */
package org.acmsl.commons.regexpplugin;

/*
 * Importing some ACM-SL classes.
 */
import org.acmsl.commons.Literals;
import org.acmsl.commons.patterns.Manager;
import org.acmsl.commons.patterns.Singleton;
import org.acmsl.commons.utils.ReflectionUtils;

/*
 * Importing some commons-logging classes.
 */
import org.apache.commons.logging.LogFactory;

/*
 * Importing JetBrains annotations.
 */
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/*
 * Importing JDK classes.
 */
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;

/*
 * Importing checkthread.org annotations.
 */
import org.checkthread.annotations.ThreadSafe;

/**
 * Manages which regexp engine to use, acting as a facade hiding all
 * details of building or retrieving implementations.
 * @author <a href="mailto:chous@acm-sl.org">Jose San Leandro Armendariz</a>
 */
@ThreadSafe
public class RegexpManager implements Manager, Singleton {
    /**
     * Configures whether to use class loaders or not.
     */
    private boolean m__bUseClassLoader;

    /**
     * The default engine.
     */
    public static final String DEFAULT_ENGINE = "org.acmsl.commons.regexpplugin.jakartaoro.ORORegexpEngine";

    /**
     * The name of the property used to identify the implementation
     * class name.
     */
    public static final String ENGINE_PROPERTY = "org.acmsl.commons.regexpplugin.RegexpEngine";

    /**
     * The name of the properties file to search for.
     */
    public static final String CONFIGURATION_SETTINGS = "regexpplugin.properties";

    /**
     * JDK1.3+ <a href="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#Service%20Provider"
     * >'Service Provider' specification</a>.
     */
    protected static final String SERVICE_ID = "META-INF/services/org.acmsl.regexpplugin.RegexpEngine";

    /**
     * The cached engines.
     */
    private static final Map<ClassLoader, RegexpEngine> m__htCachedEngines = new Hashtable<ClassLoader, RegexpEngine>();

    /**
     * Singleton implemented to avoid the double-checked locking.
     */
    private static class RegexpManagerSingletonContainer {
        /**
         * The actual singleton.
         */
        public static final RegexpManager SINGLETON = new RegexpManager();
    }

    /**
     * Creates a <code>RegexpManager</code> instance to use or not
     * class loaders.
     * @param useClassLoader whether to use it or not.
     */
    protected RegexpManager(final boolean useClassLoader) {
        immutableSetUsingClassLoader(useClassLoader);
    }

    /**
     * Protected constructor to avoid accidental instantiation.
     */
    protected RegexpManager() {
        this(true);
    }

    /**
     * Retrieves a <code>RegexpManager</code> instance.
     * @param useClassLoader whether to use class loader or not.
     * @return such instance.
     */
    public static RegexpManager getInstance(final boolean useClassLoader) {
        return (useClassLoader) ? new RegexpManager(useClassLoader) : getInstance();
    }

    /**
     * Retrieves a <code>RegexpManager</code> instance.
     * @return such instance.
     */
    @NotNull
    public static RegexpManager getInstance() {
        return RegexpManagerSingletonContainer.SINGLETON;
    }

    /**
     * Specifies whether to use class loader or not.
     * @param flag such flag.
     */
    protected final void immutableSetUsingClassLoader(final boolean flag) {
        m__bUseClassLoader = flag;
    }

    /**
     * Specifies whether to use class loader or not.
     * @param flag such flag.
     */
    @SuppressWarnings("unused")
    public void setUsingClassLoader(final boolean flag) {
        immutableSetUsingClassLoader(flag);
    }

    /**
     * Retrieves whether the engine is using a class loader or not.
     * @return such flag.
     */
    public boolean isUsingClassLoader() {
        return m__bUseClassLoader;
    }

    /**
     * Retrieves the cached engines.
     * @return such map.
     */
    @Nullable
    protected Map<ClassLoader, RegexpEngine> getCachedEngines() {
        return m__htCachedEngines;
    }

    /**
     * Retrieves current engine.
     * Note: The lookup mechanism is adapted from Commons-Logging.
     * @return the engine information.
     */
    @NotNull
    public RegexpEngine getEngine() {
        return getEngine(isUsingClassLoader());
    }

    /**
     * Retrieves current engine.
     * Note: The lookup mechanism is adapted from Commons-Logging.
     * @param useClassLoader whether to use class loader or not.
     * @return the engine information.
     * @throws RegexpEngineNotFoundException if no engine can be used.
     */
    @NotNull
    protected RegexpEngine getEngine(final boolean useClassLoader) throws RegexpEngineNotFoundException {
        @NotNull
        final RegexpEngine result;

        @NotNull
        ClassLoader t_ClassLoader = getClass().getClassLoader();

        if (useClassLoader) {
            // Identify the class loader we will be using
            t_ClassLoader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
                public ClassLoader run() {
                    return getContextClassLoader();
                }
            });
        }

        result = getEngineUsingClassLoader(t_ClassLoader);

        return result;
    }

    /**
     * Retrieves current engine.
     * Note: The lookup mechanism is adapted from Commons-Logging.
     * @param classLoader the class loader.
     * @return the engine information.
     * @throws RegexpEngineNotFoundException if no engine can be used.
     */
    @NotNull
    protected RegexpEngine getEngineUsingClassLoader(@NotNull final ClassLoader classLoader)
            throws RegexpEngineNotFoundException {
        @Nullable
        RegexpEngine result;

        // Return any previously registered engine for this class loader
        result = getCachedEngine(classLoader);

        InputStream t_isStream = null;

        Properties t_htProperties = null;

        if (result == null) {

            // First, try the system property
            try {
                @NotNull
                final String engineClass = System.getProperty(ENGINE_PROPERTY);

                if (engineClass != null) {
                    result = createEngine(engineClass, classLoader);
                }
            } catch (final SecurityException securityException) {
                LogFactory.getLog(RegexpManager.class)
                        .info(Literals.COULD_NOT_LOAD_ENVIRONMENT_PROPERTY + ENGINE_PROPERTY, securityException);
            }
        }

        if (result == null) {
            // Second, try to find a service by using the JDK1.3 jar
            // discovery mechanism. This will allow users to plug a logger
            // by just placing it in the lib/ directory of the webapp ( or in
            // CLASSPATH or equivalent ). This is similar to the second
            // step, except that it uses the (standard?) jdk1.3 location in the jar.

            try {
                t_isStream = getResourceAsStream(classLoader, SERVICE_ID);

                if (t_isStream != null) {
                    // This code is needed by EBCDIC and other strange systems.
                    // It's a fix for bugs reported in xerces
                    BufferedReader t_brReader;

                    try {
                        t_brReader = new BufferedReader(new InputStreamReader(t_isStream, "UTF-8"));
                    } catch (final UnsupportedEncodingException invalidEncoding) {
                        LogFactory.getLog(RegexpManager.class).info("System doesn't support UTF-8",
                                invalidEncoding);

                        t_brReader = new BufferedReader(
                                new InputStreamReader(t_isStream, Charset.defaultCharset().name()));
                    }

                    final String engineClassName = t_brReader.readLine();

                    t_brReader.close();

                    if ((engineClassName != null) && (!"".equals(engineClassName))) {
                        result = createEngine(engineClassName, classLoader);
                    }
                }
            } catch (final Exception exception) {
                LogFactory.getLog(RegexpManager.class)
                        .info("Could not find JDK1.3 service provider for RegexpPlugin", exception);
            }
        }

        if (result == null) {
            // Third try a properties file.
            // If the properties file exists, it'll be read and the properties
            // used. IMHO ( costin ) System property and JDK1.3 jar service
            // should be enough for detecting the class name. The properties
            // should be used to set the attributes ( which may be specific to
            // the webapp, even if a default logger is set at JVM level by a
            // system property )

            try {
                t_isStream = getResourceAsStream(classLoader, CONFIGURATION_SETTINGS);
            } catch (final SecurityException securityException) {
                LogFactory.getLog(RegexpManager.class).info(
                        "Could not load " + CONFIGURATION_SETTINGS + ". Trying /" + CONFIGURATION_SETTINGS,
                        securityException);
            }

            try {
                t_isStream = getResourceAsStream(classLoader, "/" + CONFIGURATION_SETTINGS);
            } catch (final SecurityException securityException) {
                LogFactory.getLog(RegexpManager.class).info("Could not load /" + CONFIGURATION_SETTINGS,
                        securityException);
            }

            if (t_isStream != null) {
                try {
                    t_htProperties = new Properties();
                    t_htProperties.load(t_isStream);
                    t_isStream.close();
                } catch (final IOException ioException) {
                    LogFactory.getLog(RegexpManager.class).info("Could not load configuration properties.",
                            ioException);
                } catch (final SecurityException securityException) {
                    LogFactory.getLog(RegexpManager.class).info("Could not load configuration properties.",
                            securityException);
                }
            }
        }

        if ((result == null) && (t_htProperties != null)) {
            @Nullable
            final String engineClass = t_htProperties.getProperty(ENGINE_PROPERTY);

            if (engineClass != null) {
                result = createEngine(engineClass, classLoader);
            }
        }

        // Fourth, try the fallback implementation class
        if (result == null) {
            result = createEngine(DEFAULT_ENGINE, classLoader); //RegexpEngine.class.getClassLoader());
        }

        if (result != null) {
            /**
             * Always cache using context class loader.
             */
            cacheEngine(classLoader, result);
        } else {
            throw new RegexpEngineNotFoundException(DEFAULT_ENGINE);
        }

        return result;
    }

    /**
     * Returns the thread context class loader if available.
     * The thread context class loader is available for JDK 1.2
     * or later, if certain security conditions are met.
     * Note: This logic is adapted from Commons-Logging.
     * @return the class loader.
     * @throws RegexpPluginMisconfiguredException if a suitable class loader
     * cannot be identified.
     */
    @NotNull
    protected ClassLoader getContextClassLoader() throws RegexpPluginMisconfiguredException {
        return getContextClassLoader(ReflectionUtils.getInstance());
    }

    /**
     * Returns the thread context class loader if available.
     * The thread context class loader is available for JDK 1.2
     * or later, if certain security conditions are met.
     * Note: This logic is adapted from Commons-Logging.
     * @param reflectionUtils the <code>ReflectionUtils</code> instance.
     * @return the class loader.
     * @throws RegexpPluginMisconfiguredException if a suitable class loader
     * cannot be identified.
     */
    @NotNull
    protected ClassLoader getContextClassLoader(@NotNull final ReflectionUtils reflectionUtils)
            throws RegexpPluginMisconfiguredException {
        @NotNull
        final ClassLoader result;

        try {
            result = reflectionUtils.getContextClassLoader();
        } catch (final IllegalAccessException illegalAccessException) {
            throw new RegexpPluginMisconfiguredException("unexpected.illegalaccessexception",
                    illegalAccessException);
        } catch (final InvocationTargetException invocationTargetException) {
            throw new RegexpPluginMisconfiguredException("unexpected.invocationtargetexception",
                    invocationTargetException.getTargetException());
        }

        // Return the selected class loader
        return result;
    }

    /**
     * Retrieves the cached engine associated to given contextClassLoader.
     * @param contextClassLoader the context class loader.
     * @return the engine.
     */
    @Nullable
    protected RegexpEngine getCachedEngine(@NotNull final ClassLoader contextClassLoader) {
        return getCachedEngine(contextClassLoader, getCachedEngines());
    }

    /**
     * Check cached engines (keyed by contextClassLoader).
     * @param contextClassLoader the context class loader.
     * @param engines the cached engines.
     * @return the {@link RegexpEngine}.
     */
    @Nullable
    protected RegexpEngine getCachedEngine(@NotNull final ClassLoader contextClassLoader,
            @NotNull final Map<ClassLoader, RegexpEngine> engines) {
        return engines.get(contextClassLoader);
    }

    /**
     * Annotates a new engine to the cache.
     * @param classLoader the key.
     * @param engine the engine.
     */
    protected void cacheEngine(@NotNull final ClassLoader classLoader, @NotNull final RegexpEngine engine) {
        cacheEngine(classLoader, engine, getCachedEngines());
    }

    /**
     * Annotates a new engine to the cache.
     * @param classLoader the key.
     * @param engine the engine.
     * @param engines the cached engines.
     */
    protected void cacheEngine(@NotNull final ClassLoader classLoader, @NotNull final RegexpEngine engine,
            @NotNull final Map<ClassLoader, RegexpEngine> engines) {
        engines.put(classLoader, engine);
    }

    /**
     * Return a new instance of the specified <code>RegexpEngine</code>
     * implementation class, loaded by the specified class loader.
     * If that fails, try the class loader used to load the
     * RegexpEngine.
     * @param engineClass Fully qualified name of the <code>RegexpEngine</code>
     * implementation class.
     * @throws RegexpEngineNotFoundException if a suitable instance
     * cannot be created.
     * @throws RegexpPluginMisconfiguredException if RegexpPlugin is
     * misconfigured.
     * @return the {@link RegexpEngine}.
     */
    @SuppressWarnings("unused")
    public RegexpEngine createEngine(@NotNull final String engineClass)
            throws RegexpEngineNotFoundException, RegexpPluginMisconfiguredException {
        return createEngine(engineClass, getContextClassLoader());
    }

    /**
     * Return a new instance of the specified <code>RegexpEngine</code>
     * implementation class, loaded by the specified class loader.
     * If that fails, try the class loader used to load the
     * RegexpEngine.
     * @param engineClass Fully qualified name of the <code>RegexpEngine</code>
     * implementation class.
     * @param classLoader ClassLoader from which to load this class.
     * @throws RegexpEngineNotFoundException if a suitable instance
     * cannot be created.
     * @throws RegexpPluginMisconfiguredException if RegexpPlugin is
     * misconfigured.
     * @return the {@link RegexpEngine}.
     */
    @SuppressWarnings("unchecked")
    @NotNull
    protected RegexpEngine createEngine(final String engineClass, final ClassLoader classLoader)
            throws RegexpEngineNotFoundException, RegexpPluginMisconfiguredException {
        @Nullable
        final RegexpEngine result = AccessController.doPrivileged(new PrivilegedAction<RegexpEngine>() {
            public RegexpEngine run() {
                RegexpEngine innerResult = null;

                final RegexpPluginMisconfiguredException exception;

                // This will be used to diagnose bad configurations
                // and allow a useful message to be sent to the user
                Class<RegexpEngine> t_RegexpEngineClass = null;

                try {
                    if (classLoader != null) {
                        try {
                            // First the given class loader param
                            // (thread class loader)
                            // Warning: must typecast here & allow
                            // exception to be generated/caught &
                            // recast properly.
                            t_RegexpEngineClass = (Class<RegexpEngine>) classLoader.loadClass(engineClass);

                            innerResult = t_RegexpEngineClass.newInstance();

                        } catch (final ClassNotFoundException classNotFoundException) {
                            if (classLoader == RegexpEngine.class.getClassLoader()) {
                                // Nothing more to try, onwards.
                                throw classNotFoundException;
                            }
                            // ignore exception, continue
                        } catch (final NoClassDefFoundError noClassDefFoundException) {
                            if (classLoader == RegexpEngine.class.getClassLoader()) {
                                // Nothing more to try, onwards.
                                throw noClassDefFoundException;
                            }
                            // ignore exception, continue
                        } catch (final ClassCastException classCastException) {
                            if (classLoader == RegexpEngine.class.getClassLoader()) {
                                // Nothing more to try, onwards (bug in
                                // loader implementation).
                                throw classCastException;
                            }
                            // Ignore exception, continue
                        }
                    }

                    if (innerResult == null) {
                        /* At this point, either classLoader == null, OR
                         * classLoader was unable to load engineClass.
                         * Try the class loader that loaded this class:
                         * RegexpEngine.getClassLoader().
                         *
                         * Notes:
                         * a) RegexpEngine.class.getClassLoader() may return
                         *    'null' if RegexpEngine is loaded by the bootstrap
                         *    classloader.
                         * b) The Java endorsed library mechanism is instead
                         *    Class.forName(engineClass);
                         */

                        // Warning: must typecast here & allow exception
                        // to be generated/caught & recast properly.
                        t_RegexpEngineClass = (Class<RegexpEngine>) Class.forName(engineClass);

                        innerResult = t_RegexpEngineClass.newInstance();
                    }
                } catch (final Exception otherException) {
                    // Check to see if we've got a bad configuration
                    if ((t_RegexpEngineClass != null)
                            && (!RegexpEngine.class.isAssignableFrom(t_RegexpEngineClass))) {
                        exception = new RegexpPluginMisconfiguredException(
                                "implementation.does.not." + "implement.regexpegine", otherException);
                    } else {
                        exception = new RegexpPluginMisconfiguredException("unexpected.problem", otherException);
                    }

                    throw exception;
                }

                return innerResult;
            }
        });

        if (result == null) {
            throw new RegexpEngineNotFoundException(engineClass);
        }

        return result;
    }

    /**
     * Retrieves the stream associated to the resource
     * whose name is given, using a concrete class loader.
     * @param loader the class loader.
     * @param name the resource name.
     * @return the stream.
     */
    @Nullable
    protected InputStream getResourceAsStream(@Nullable final ClassLoader loader, @NotNull final String name) {
        return AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
            public InputStream run() {
                final InputStream result;

                if (loader != null) {
                    result = loader.getResourceAsStream(name);
                } else {
                    result = ClassLoader.getSystemResourceAsStream(name);
                }

                return result;
            }
        });
    }

    @Override
    public String toString() {
        return "RegexpManager{" + "useClassLoader=" + m__bUseClassLoader + '}';
    }
}