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    }