    001package org.apache.turbine.services;
    002
    003
    004/*
    005 * Licensed to the Apache Software Foundation (ASF) under one
    006 * or more contributor license agreements.  See the NOTICE file
    007 * distributed with this work for additional information
    008 * regarding copyright ownership.  The ASF licenses this file
    009 * to you under the Apache License, Version 2.0 (the
    010 * "License"); you may not use this file except in compliance
    011 * with the License.  You may obtain a copy of the License at
    012 *
    013 *   http://www.apache.org/licenses/LICENSE-2.0
    014 *
    015 * Unless required by applicable law or agreed to in writing,
    016 * software distributed under the License is distributed on an
    017 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    018 * KIND, either express or implied.  See the License for the
    019 * specific language governing permissions and limitations
    020 * under the License.
    021 */
    022
    023
    024import java.util.Hashtable;
    025import java.util.Stack;
    026
    027import org.apache.commons.logging.Log;
    028import org.apache.commons.logging.LogFactory;
    029
    030/**
    031 * A generic implementation of <code>InitableBroker</code>.
    032 * Functionality provided by the broker includes:
    033 *
    034 * <ul>
    035 *
    036 * <li>Maintaining single instance of each <code>Initable</code> in
    037 * the system.</li>
    038 *
    039 * <li>Early initialization of <code>Initables</code> during system
    040 * startup.</li>
    041 *
    042 * <li>Late initialization of <code>Initables</code> before they are
    043 * used.</li>
    044 *
    045 * <li>Providing instances of <code>Initables</code> to requesting
    046 * parties.</li>
    047 *
    048 * <li>Maintaining dependencies between <code>Initables</code> during
    049 * early initalization phases, including circular dependencies
    050 * detection.</li>
    051 *
    052 * </ul>
    053 *
    054 * @author <a href="mailto:burton@apache.org">Kevin Burton</a>
    055 * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
    056 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
    057 * @version $Id: BaseInitableBroker.java 1706239 2015-10-01 13:18:35Z tv $
    058 */
    059public abstract class BaseInitableBroker
    060        implements InitableBroker
    061{
    062    /** A repository of Initable instances. */
    063    protected Hashtable<String, Initable> initables = new Hashtable<String, Initable>();
    064
    065    /**
    066     * Names of classes being early-initialized are pushed onto this
    067     * stack.  A name appearing twice indicates a circular dependency
    068     * chain.
    069     */
    070    protected Stack<String> stack = new Stack<String>();
    071
    072    /** Logging */
    073    private final Log log = LogFactory.getLog(this.getClass());
    074
    075    /**
    076     * Default constructor of InitableBroker.
    077     *
    078     * This constructor does nothing. Your brokers should be
    079     * singletons, therefore their constructors should be
    080     * private. They should also have public YourBroker getInstance()
    081     * methods.
    082     */
    083    protected BaseInitableBroker()
    084    {
    085        // empty
    086    }
    087
    088    /**
    089     * Performs early initialization of an Initable class.
    090     *
    091     * @param className The name of the class to be initialized.
    092     * @param data An Object to be used for initialization activities.
    093     * @exception InitializationException Initialization was not successful.
    094     */
    095    @Override
    096    public void initClass(String className, Object data)
    097            throws InitializationException
    098    {
    099        // make sure that only one thread calls this method recursively
    100        synchronized (stack)
    101        {
    102            int pos = stack.search(className);
    103            if (pos != -1)
    104            {
    105                StringBuilder msg = new StringBuilder().append(className)
    106                        .append(" couldn't be initialized because of circular dependency chain:\n");
    107                for (int i = pos; i > 0; i--)
    108                {
    109                    msg.append(stack.elementAt(stack.size() - i - 1) + "->");
    110                }
    111                msg.append(className).append('\n');
    112
    113                throw new InitializationException(msg.toString());
    114            }
    115            try
    116            {
    117                stack.push(className);
    118                Initable instance = getInitableInstance(className);
    119                if (!instance.getInit())
    120                {
    121                    // this call might result in an indirect recursion
    122                    instance.init(data);
    123                }
    124            }
    125            finally
    126            {
    127                // Succeeded or not, make sure the name gets off the stack.
    128                stack.pop();
    129            }
    130        }
    131    }
    132
    133    /**
    134     * Shuts down an <code>Initable</code>.
    135     *
    136     * This method is used to release resources allocated by an
    137     * <code>Initable</code>, and return it to its initial (uninitialized)
    138     * state.
    139     *
    140     * @param className The name of the class to be uninitialized.
    141     */
    142    @Override
    143    public void shutdownClass(String className)
    144    {
    145        try
    146        {
    147            Initable initable = getInitableInstance(className);
    148            if (initable.getInit())
    149            {
    150                initable.shutdown();
    151                ((BaseInitable) initable).setInit(false);
    152            }
    153        }
    154        catch (InstantiationException e)
    155        {
    156            // Shutdown of a nonexistent class was requested.
    157            // This does not hurt anything, so we log the error and continue.
    158            log.error("Shutdown of a nonexistent class " +
    159                    className + " was requested", e);
    160        }
    161    }
    162
    163    /**
    164     * Provides an instance of Initable class ready to work.
    165     *
    166     * If the requested class couldn't be instantiated or initialized,
    167     * an InstantiationException will be thrown. You needn't handle
    168     * this exception in your code, since it indicates fatal
    169     * misconfiguration of the system.
    170     *
    171     * @param className The name of the Initable requested.
    172     * @return An instance of the requested Initable.
    173     * @exception InstantiationException if there was a problem
    174     * during instantiation or initialization of the Initable.
    175     */
    176    @Override
    177    public Initable getInitable(String className)
    178            throws InstantiationException
    179    {
    180        Initable initable;
    181        try
    182        {
    183            initable = getInitableInstance(className);
    184            if (!initable.getInit())
    185            {
    186                synchronized (initable.getClass())
    187                {
    188                    if (!initable.getInit())
    189                    {
    190                        initable.init();
    191                    }
    192                    if (!initable.getInit())
    193                    {
    194                        // this exception will be caught & rethrown by this
    195                        // very method. getInit() returning false indicates
    196                        // some initialization issue, which in turn prevents
    197                        // the InitableBroker from passing a working
    198                        // instance of the initable to the client.
    199                        throw new InitializationException(
    200                                "init() failed to initialize class "
    201                                + className);
    202                    }
    203                }
    204            }
    205            return initable;
    206        }
    207        catch (InitializationException e)
    208        {
    209            throw new InstantiationException("Class " + className +
    210                    " failed to initialize", e);
    211        }
    212    }
    213
    214    /**
    215     * Retrieves an instance of an Initable from the repository.
    216     *
    217     * If the requested class is not present in the repository, it is
    218     * instantiated and passed a reference to the broker, saved and
    219     * then returned.
    220     *
    221     * @param className The name of the class to be instantiated.
    222     * @exception InstantiationException if the requested class can't
    223     * be instantiated.
    224     */
    225    protected Initable getInitableInstance(String className)
    226            throws InstantiationException
    227    {
    228        Initable initable = initables.get(className);
    229
    230        if (initable == null)
    231        {
    232            try
    233            {
    234                initable = (Initable) Class.forName(className).newInstance();
    235            }
    236
    237                    // those two errors must be passed to the VM
    238            catch (ThreadDeath t)
    239            {
    240                throw t;
    241            }
    242            catch (OutOfMemoryError t)
    243            {
    244                throw t;
    245            }
    246
    247            catch (Throwable t)
    248            {
    249                // Used to indicate error condition.
    250                String msg = null;
    251
    252                if (t instanceof NoClassDefFoundError)
    253                {
    254                    msg = "A class referenced by " + className +
    255                            " is unavailable. Check your jars and classes.";
    256                }
    257                else if (t instanceof ClassNotFoundException)
    258                {
    259                    msg = "Class " + className +
    260                            " is unavailable. Check your jars and classes.";
    261                }
    262                else if (t instanceof ClassCastException)
    263                {
    264                    msg = "Class " + className +
    265                            " doesn't implement Initable.";
    266                }
    267                else
    268                {
    269                    msg = "Failed to instantiate " + className;
    270                }
    271
    272                throw new InstantiationException(msg, t);
    273            }
    274
    275            initable.setInitableBroker(this);
    276            initables.put(className, initable);
    277        }
    278
    279        return initable;
    280    }
    281
    282}