View Javadoc
1   package org.apache.turbine.services;
2   
3   
4   /*
5    * Licensed to the Apache Software Foundation (ASF) under one
6    * or more contributor license agreements.  See the NOTICE file
7    * distributed with this work for additional information
8    * regarding copyright ownership.  The ASF licenses this file
9    * to you under the Apache License, Version 2.0 (the
10   * "License"); you may not use this file except in compliance
11   * with the License.  You may obtain a copy of the License at
12   *
13   *   http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing,
16   * software distributed under the License is distributed on an
17   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18   * KIND, either express or implied.  See the License for the
19   * specific language governing permissions and limitations
20   * under the License.
21   */
22  
23  
24  import java.util.Hashtable;
25  import java.util.Stack;
26  
27  import org.apache.logging.log4j.LogManager;
28  import org.apache.logging.log4j.Logger;
29  
30  /**
31   * A generic implementation of <code>InitableBroker</code>.
32   * Functionality provided by the broker includes:
33   *
34   * <ul>
35   *
36   * <li>Maintaining single instance of each <code>Initable</code> in
37   * the system.</li>
38   *
39   * <li>Early initialization of <code>Initables</code> during system
40   * startup.</li>
41   *
42   * <li>Late initialization of <code>Initables</code> before they are
43   * used.</li>
44   *
45   * <li>Providing instances of <code>Initables</code> to requesting
46   * parties.</li>
47   *
48   * <li>Maintaining dependencies between <code>Initables</code> during
49   * early initialization phases, including circular dependencies
50   * detection.</li>
51   *
52   * </ul>
53   *
54   * @author <a href="mailto:burton@apache.org">Kevin Burton</a>
55   * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
56   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
57   * @version $Id$
58   */
59  public abstract class BaseInitableBroker
60          implements InitableBroker
61  {
62      /** A repository of Initable instances. */
63      protected Hashtable<String, Initable> initables = new Hashtable<>();
64  
65      /**
66       * Names of classes being early-initialized are pushed onto this
67       * stack.  A name appearing twice indicates a circular dependency
68       * chain.
69       */
70      protected Stack<String> stack = new Stack<>();
71  
72      /** Logging */
73      private final Logger log = LogManager.getLogger(this.getClass());
74  
75      /**
76       * Default constructor of InitableBroker.
77       *
78       * This constructor does nothing. Your brokers should be
79       * singletons, therefore their constructors should be
80       * private. They should also have public YourBroker getInstance()
81       * methods.
82       */
83      protected BaseInitableBroker()
84      {
85          // empty
86      }
87  
88      /**
89       * Performs early initialization of an Initable class.
90       *
91       * @param className The name of the class to be initialized.
92       * @param data An Object to be used for initialization activities.
93       * @throws InitializationException Initialization was not successful.
94       */
95      @Override
96      public void initClass(String className, Object data)
97              throws InitializationException
98      {
99          // 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 {} was requested", className, e);
159         }
160     }
161 
162     /**
163      * Provides an instance of Initable class ready to work.
164      *
165      * If the requested class couldn't be instantiated or initialized,
166      * an InstantiationException will be thrown. You needn't handle
167      * this exception in your code, since it indicates fatal
168      * misconfiguration of the system.
169      *
170      * @param className The name of the Initable requested.
171      * @return An instance of the requested Initable.
172      * @throws InstantiationException if there was a problem
173      * during instantiation or initialization of the Initable.
174      */
175     @Override
176     public Initable getInitable(String className)
177             throws InstantiationException
178     {
179         Initable initable;
180         try
181         {
182             initable = getInitableInstance(className);
183             if (!initable.getInit())
184             {
185                 synchronized (initable.getClass())
186                 {
187                     if (!initable.getInit())
188                     {
189                         initable.init();
190                     }
191                     if (!initable.getInit())
192                     {
193                         // this exception will be caught & rethrown by this
194                         // very method. getInit() returning false indicates
195                         // some initialization issue, which in turn prevents
196                         // the InitableBroker from passing a working
197                         // instance of the initable to the client.
198                         throw new InitializationException(
199                                 "init() failed to initialize class "
200                                 + className);
201                     }
202                 }
203             }
204             return initable;
205         }
206         catch (InitializationException e)
207         {
208             throw new InstantiationException("Class " + className +
209                     " failed to initialize", e);
210         }
211     }
212 
213     /**
214      * Retrieves an instance of an Initable from the repository.
215      *
216      * If the requested class is not present in the repository, it is
217      * instantiated and passed a reference to the broker, saved and
218      * then returned.
219      *
220      * @param className The name of the class to be instantiated.
221      *
222      * @return an instance of the requested class
223      *
224      * @throws InstantiationException if the requested class can't
225      * be instantiated.
226      */
227     protected Initable getInitableInstance(String className)
228             throws InstantiationException
229     {
230         Initable initable = initables.get(className);
231 
232         if (initable == null)
233         {
234             try
235             {
236                 initable = (Initable) Class.forName(className).newInstance();
237             }
238 
239                     // those two errors must be passed to the VM
240             catch (ThreadDeath t)
241             {
242                 throw t;
243             }
244             catch (OutOfMemoryError t)
245             {
246                 throw t;
247             }
248 
249             catch (Throwable t)
250             {
251                 // Used to indicate error condition.
252                 String msg = null;
253 
254                 if (t instanceof NoClassDefFoundError)
255                 {
256                     msg = "A class referenced by " + className +
257                             " is unavailable. Check your jars and classes.";
258                 }
259                 else if (t instanceof ClassNotFoundException)
260                 {
261                     msg = "Class " + className +
262                             " is unavailable. Check your jars and classes.";
263                 }
264                 else if (t instanceof ClassCastException)
265                 {
266                     msg = "Class " + className +
267                             " doesn't implement Initable.";
268                 }
269                 else
270                 {
271                     msg = "Failed to instantiate " + className;
272                 }
273 
274                 throw new InstantiationException(msg, t);
275             }
276 
277             initable.setInitableBroker(this);
278             initables.put(className, initable);
279         }
280 
281         return initable;
282     }
283 
284 }