    001    package 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
    024    import java.util.Hashtable;
    025    import java.util.Stack;
    026
    027    import org.apache.commons.logging.Log;
    028    import 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 1078552 2011-03-06 19:58:46Z tv $
    058     */
    059    public 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        public void initClass(String className, Object data)
    096                throws InitializationException
    097        {
    098            // make sure that only one thread calls this method recursively
    099            synchronized (stack)
    100            {
    101                int pos = stack.search(className);
    102                if (pos != -1)
    103                {
    104                    StringBuffer msg = new StringBuffer().append(className)
    105                            .append(" couldn't be initialized because of circular depency chain:\n");
    106                    for (int i = pos; i > 0; i--)
    107                    {
    108                        msg.append(stack.elementAt(stack.size() - i - 1) + "->");
    109                    }
    110                    msg.append(className).append('\n');
    111
    112                    throw new InitializationException(msg.toString());
    113                }
    114                try
    115                {
    116                    stack.push(className);
    117                    Initable instance = getInitableInstance(className);
    118                    if (!instance.getInit())
    119                    {
    120                        // this call might result in an indirect recursion
    121                        instance.init(data);
    122                    }
    123                }
    124                finally
    125                {
    126                    // Succeeded or not, make sure the name gets off the stack.
    127                    stack.pop();
    128                }
    129            }
    130        }
    131
    132        /**
    133         * Shuts down an <code>Initable</code>.
    134         *
    135         * This method is used to release resources allocated by an
    136         * <code>Initable</code>, and return it to its initial (uninitailized)
    137         * state.
    138         *
    139         * @param className The name of the class to be uninitialized.
    140         */
    141        public void shutdownClass(String className)
    142        {
    143            try
    144            {
    145                Initable initable = getInitableInstance(className);
    146                if (initable.getInit())
    147                {
    148                    initable.shutdown();
    149                    ((BaseInitable) initable).setInit(false);
    150                }
    151            }
    152            catch (InstantiationException e)
    153            {
    154                // Shutdown of a nonexistent class was requested.
    155                // This does not hurt anything, so we log the error and continue.
    156                log.error("Shutdown of a nonexistent class " +
    157                        className + " was requested", e);
    158            }
    159        }
    160
    161        /**
    162         * Provides an instance of Initable class ready to work.
    163         *
    164         * If the requested class couldn't be instatiated or initialized,
    165         * an InstantiationException will be thrown. You needn't handle
    166         * this exception in your code, since it indicates fatal
    167         * misconfigurtion of the system.
    168         *
    169         * @param className The name of the Initable requested.
    170         * @return An instance of the requested Initable.
    171         * @exception InstantiationException if there was a problem
    172         * during instantiation or initialization of the Initable.
    173         */
    174        public Initable getInitable(String className)
    175                throws InstantiationException
    176        {
    177            Initable initable;
    178            try
    179            {
    180                initable = getInitableInstance(className);
    181                if (!initable.getInit())
    182                {
    183                    synchronized (initable.getClass())
    184                    {
    185                        if (!initable.getInit())
    186                        {
    187                            initable.init();
    188                        }
    189                        if (!initable.getInit())
    190                        {
    191                            // this exception will be caught & rethrown by this
    192                            // very method. getInit() returning false indicates
    193                            // some initialization issue, which in turn prevents
    194                            // the InitableBroker from passing a working
    195                            // instance of the initable to the client.
    196                            throw new InitializationException(
    197                                    "init() failed to initialize class "
    198                                    + className);
    199                        }
    200                    }
    201                }
    202                return initable;
    203            }
    204            catch (InitializationException e)
    205            {
    206                throw new InstantiationException("Class " + className +
    207                        " failed to initialize", e);
    208            }
    209        }
    210
    211        /**
    212         * Retrieves an instance of an Initable from the repository.
    213         *
    214         * If the requested class is not present in the repository, it is
    215         * instantiated and passed a reference to the broker, saved and
    216         * then returned.
    217         *
    218         * @param className The name of the class to be instantiated.
    219         * @exception InstantiationException if the requested class can't
    220         * be instantiated.
    221         */
    222        protected Initable getInitableInstance(String className)
    223                throws InstantiationException
    224        {
    225            Initable initable = initables.get(className);
    226
    227            if (initable == null)
    228            {
    229                try
    230                {
    231                    initable = (Initable) Class.forName(className).newInstance();
    232                }
    233
    234                        // those two errors must be passed to the VM
    235                catch (ThreadDeath t)
    236                {
    237                    throw t;
    238                }
    239                catch (OutOfMemoryError t)
    240                {
    241                    throw t;
    242                }
    243
    244                catch (Throwable t)
    245                {
    246                    // Used to indicate error condition.
    247                    String msg = null;
    248
    249                    if (t instanceof NoClassDefFoundError)
    250                    {
    251                        msg = "A class referenced by " + className +
    252                                " is unavailable. Check your jars and classes.";
    253                    }
    254                    else if (t instanceof ClassNotFoundException)
    255                    {
    256                        msg = "Class " + className +
    257                                " is unavailable. Check your jars and classes.";
    258                    }
    259                    else if (t instanceof ClassCastException)
    260                    {
    261                        msg = "Class " + className +
    262                                " doesn't implement Initable.";
    263                    }
    264                    else
    265                    {
    266                        msg = "Failed to instantiate " + className;
    267                    }
    268
    269                    throw new InstantiationException(msg, t);
    270                }
    271
    272                initable.setInitableBroker(this);
    273                initables.put(className, initable);
    274            }
    275
    276            return initable;
    277        }
    278
    279    }