1   /*
2    * Copyright 2003 - 2013 The eFaps Team
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Revision:        $Rev$
17   * Last Changed:    $Date$
18   * Last Changed By: $Author$
19   */
20  
21  package org.efaps.admin.runlevel;
22  
23  import java.lang.reflect.InvocationTargetException;
24  import java.lang.reflect.Method;
25  import java.sql.ResultSet;
26  import java.sql.SQLException;
27  import java.sql.Statement;
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  
33  import org.efaps.admin.common.Quartz;
34  import org.efaps.db.Context;
35  import org.efaps.db.transaction.ConnectionResource;
36  import org.efaps.db.wrapper.SQLPart;
37  import org.efaps.db.wrapper.SQLSelect;
38  import org.efaps.util.EFapsException;
39  import org.efaps.util.cache.AbstractCache;
40  import org.efaps.util.cache.InfinispanCache;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  /**
45   * This Class is the run level for eFaps. It provides the possibility to load
46   * only the specified or needed parts into the Cache. It can be defined within
47   * the database.
48   *
49   * @author The eFaps Team
50   * @version $Id$
51   */
52  public final class RunLevel
53  {
54      /**
55       * Logger for this class.
56       */
57      private static final Logger LOG = LoggerFactory.getLogger(RunLevel.class);
58  
59      /**
60       * Name of SQL table used to test if the run level is already installed in
61       * the database.
62       *
63       * @see #isInitialisable()
64       */
65      private static final String TABLE_TESTS = "T_RUNLEVEL";
66  
67      /**
68       * This is the SQL select statement to select a RunLevel from the database.
69       *
70       * @see #RunLevel(long)
71       * @see #RunLevel(String)
72       */
73      private static final SQLSelect SELECT_RUNLEVEL = new SQLSelect()
74                                                              .column(0, "ID")
75                                                              .column(0, "PARENT")
76                                                              .from("T_RUNLEVEL", 0);
77  
78      /**
79       * SQL select statement to select a RunLevel from the database.
80       *
81       * @see #initialize(String)
82       */
83      private static final SQLSelect SELECT_DEF_PRE = new SQLSelect()
84                                                              .column(0, "CLASS")
85                                                              .column(0, "METHOD")
86                                                              .column(0, "PARAMETER")
87                                                              .from("T_RUNLEVELDEF", 0);
88  
89      /**
90       * Current RunLevel.
91       */
92      private static RunLevel RUNLEVEL = null;
93  
94      /**
95       * Mapping of all RunLevels to id.
96       */
97      private static final Map<Long, RunLevel> ALL_RUNLEVELS = new HashMap<Long, RunLevel>();
98  
99      /**
100      * All cache initialize methods for this RunLevel are stored in this instance
101      * variable. They are ordered by the priority.
102      */
103     private final List<CacheMethod> cacheMethods = new ArrayList<CacheMethod>();
104 
105     /**
106      * The id in the eFaps database of this run level.
107      */
108     private long id = 0;
109 
110     /**
111      * The parent run level.
112      */
113     private RunLevel parent;
114 
115     /**
116      * Name of this RunLevel.
117      */
118     private String name;
119 
120     /**
121      * Initializes this run level instance depending on the <code>_name</code>.
122      *
123      * @param _name   name of the run level
124      * @throws EFapsException on error
125      */
126     private RunLevel(final String _name)
127         throws EFapsException
128     {
129         this.name = _name;
130         initialize(RunLevel.SELECT_RUNLEVEL.getCopy().addPart(SQLPart.WHERE).addColumnPart(0, "RUNLEVEL")
131                         .addPart(SQLPart.EQUAL).addEscapedValuePart(_name).getSQL());
132     }
133 
134     /**
135      * Initializes this run level instance depending on the <code>_id</code>.
136      *
137      * @param _id       id of the run level
138      * @throws EFapsException on error
139      */
140     private RunLevel(final long _id)
141         throws EFapsException
142     {
143         initialize(RunLevel.SELECT_RUNLEVEL.getCopy().addPart(SQLPart.WHERE).addColumnPart(0, "ID")
144                         .addPart(SQLPart.EQUAL).addValuePart(_id).getSQL());
145     }
146 
147     /**
148      * The static method first removes all values in the caches. Then the cache
149      * is initialized automatically depending on the desired RunLevel
150      *
151      * @param _runLevel   name of run level to initialize
152      * @throws EFapsException on error
153      */
154     public static void init(final String _runLevel)
155         throws EFapsException
156     {
157         RunLevel.ALL_RUNLEVELS.clear();
158         RunLevel.RUNLEVEL = new RunLevel(_runLevel);
159     }
160 
161     /**
162      * Stops all RunLevel.
163      */
164     public static void stop()
165     {
166         InfinispanCache.stop();
167         Quartz.shutDown();
168     }
169 
170 
171     /**
172      * @return Return the name of the currenct active RunLevel.
173      */
174     public static String getName4Current()
175     {
176         return RunLevel.RUNLEVEL == null ? null : RunLevel.RUNLEVEL.name;
177     }
178 
179     /**
180      * Tests, if the SQL table {@link #TABLE_TESTS} exists (= <i>true</i>).
181      * This means the run level could be initialized.
182      *
183      * @return <i>true</i> if a run level could be initialized (and the SQL
184      *         table exists in the database); otherwise <i>false</i>
185      * @throws EFapsException if the test for the table fails
186      * @see #TABLE_TESTS
187      */
188     public static boolean isInitialisable()
189         throws EFapsException
190     {
191         try {
192             return Context.getDbType().existsTable(Context.getThreadContext().getConnection(),
193                                                    RunLevel.TABLE_TESTS);
194         } catch (final SQLException e) {
195             throw new EFapsException(RunLevel.class, "isInitialisable.SQLException", e);
196         }
197     }
198 
199     /**
200      * Execute the current RunLevel. (Load all defined Caches).
201      *
202      * @throws EFapsException on error
203      */
204     public static void execute()
205         throws EFapsException
206     {
207         RunLevel.RUNLEVEL.executeMethods();
208         final List<String> allInitializer = RunLevel.RUNLEVEL.getAllInitializers();
209         for (final AbstractCache<?> cache : AbstractCache.getCaches()) {
210             final String initiliazer = cache.getInitializer();
211             if (!allInitializer.contains(initiliazer)) {
212                 cache.clear();
213             }
214         }
215         //Bpm.initialize();
216     }
217 
218     /**
219      * Returns the list of all initializers.
220      *
221      * @return list of all initializers
222      */
223     private List<String> getAllInitializers()
224     {
225         final List<String> ret = new ArrayList<String>();
226         for (final CacheMethod cacheMethod : this.cacheMethods) {
227             ret.add(cacheMethod.className);
228         }
229         if (this.parent != null) {
230             ret.addAll(this.parent.getAllInitializers());
231         }
232         return ret;
233     }
234 
235     /**
236      * All cache initialize methods stored in {@link #cacheMethods} are called.
237      *
238      * @see #cacheMethods
239      * @throws EFapsException on error
240      */
241     protected void executeMethods()
242         throws EFapsException
243     {
244         if (this.parent != null) {
245             this.parent.executeMethods();
246         }
247         for (final CacheMethod cacheMethod : this.cacheMethods) {
248             cacheMethod.callMethod();
249         }
250     }
251 
252     /**
253      * Reads the id and the parent id of this RunLevel. All defined methods for
254      * this run level are loaded. If a parent id is defined, the parent is
255      * initialized.
256      *
257      * @param _sql  SQL statement to get the id and parent id for this run
258      *              level
259      * @see #parent
260      * @see #cacheMethods
261      * @throws EFapsException on error
262      */
263     protected void initialize(final String _sql)
264         throws EFapsException
265     {
266         ConnectionResource con = null;
267         try {
268             con = Context.getThreadContext().getConnectionResource();
269             Statement stmt = null;
270             long parentId = 0;
271 
272             try {
273                 stmt = con.getConnection().createStatement();
274                 // read run level itself
275                 ResultSet rs = stmt.executeQuery(_sql);
276                 if (rs.next()) {
277                     this.id = rs.getLong(1);
278                     parentId = rs.getLong(2);
279                 } else {
280                     RunLevel.LOG.error("RunLevel not found");
281                 }
282                 rs.close();
283 
284                 // read all methods for one run level
285                 rs = stmt.executeQuery(RunLevel.SELECT_DEF_PRE.getCopy()
286                                 .addPart(SQLPart.WHERE)
287                                 .addColumnPart(0, "RUNLEVELID")
288                                 .addPart(SQLPart.EQUAL)
289                                 .addValuePart(this.id)
290                                 .addPart(SQLPart.ORDERBY)
291                                 .addColumnPart(0, "PRIORITY").getSQL());
292 
293                 /**
294                  * Order part of the SQL select statement.
295                  */
296                 //private static final String SQL_DEF_POST  = " order by PRIORITY";
297 
298                 while (rs.next()) {
299                     if (rs.getString(3) != null) {
300                         this.cacheMethods.add(new CacheMethod(rs.getString(1).trim(),
301                                                               rs.getString(2).trim(),
302                                                               rs.getString(3).trim()));
303                     } else {
304                         this.cacheMethods.add(new CacheMethod(rs.getString(1).trim(),
305                                                               rs.getString(2).trim()));
306                     }
307                 }
308                 rs.close();
309             } finally {
310                 if (stmt != null) {
311                     stmt.close();
312                 }
313             }
314             con.commit();
315             RunLevel.ALL_RUNLEVELS.put(this.id, this);
316             if (parentId != 0) {
317                 this.parent = RunLevel.ALL_RUNLEVELS.get(parentId);
318                 if (this.parent == null) {
319                     this.parent = new RunLevel(parentId);
320                 }
321             }
322         } catch (final EFapsException e) {
323             RunLevel.LOG.error("initialise()", e);
324         } catch (final SQLException e) {
325             RunLevel.LOG.error("initialise()", e);
326         } finally {
327             if ((con != null) && con.isOpened()) {
328                 con.abort();
329             }
330         }
331     }
332 
333     /**
334      * Cache for the methods, which are defined for the run level. The stored
335      * string values can be used to invoke the methods. Therefore the cache is
336      * separated in three fields:
337      * <ul>
338      * <li><b>CLASSNAME</b>: name of the class as written in a java class </li>
339      * <li><b>METHODNAME</b>: name of the a static method, it can optional used
340      *     with one string parameter</li>
341      * <li><b>PARAMETER</b> <i>(optional)</i>: the string value corresponding
342      *     with the method</li>
343      * </ul>
344      */
345     public static final class CacheMethod
346     {
347         /**
348          * Name of the class which must be initialized.
349          */
350         private final String className;
351 
352         /**
353          * Name of the static method used to initialized the cache.
354          */
355         private final String methodName;
356 
357         /**
358          * Parameter for the static method used to initialized the cache.
359          */
360         private final String parameter;
361 
362         /**
363          *  Constructor for the {@link CacheMethod} in the case that there are only
364          * {@link #className} and {@link #methodName}.
365          *
366          * @param _className   name of the class
367          * @param _methodName  name of the method
368          * @see #CacheMethod(String,String,String)
369          */
370         private CacheMethod(final String _className,
371                             final String _methodName)
372         {
373             this(_className, _methodName, null);
374         }
375 
376         /**
377          * Constructor for the cache with {@link #className},
378          * {@link #methodName} and {@link #parameter}.
379          *
380          * @param _className    Name of the Class
381          * @param _methodName   Name of the Method
382          * @param _parameter    Value of the Parameter
383          */
384         private CacheMethod(final String _className,
385                             final String _methodName,
386                             final String _parameter)
387         {
388             this.className = _className;
389             this.methodName = _methodName;
390             this.parameter = _parameter;
391         }
392 
393         /**
394          * Calls the static cache initialize method defined by this instance.
395          *
396          * @throws EFapsException on error
397          */
398         public void callMethod()
399             throws EFapsException
400         {
401             try {
402                 final Class<?> cls = Class.forName(this.className);
403                 if (this.parameter != null) {
404                     final Method m = cls.getMethod(this.methodName, String.class);
405                     m.invoke(cls, this.parameter);
406                 } else {
407                     final Method m = cls.getMethod(this.methodName, new Class[] {});
408                     m.invoke(cls);
409                 }
410             } catch (final ClassNotFoundException e) {
411                 RunLevel.LOG.error("class '" + this.className + "' not found", e);
412                 throw new EFapsException(getClass(),
413                                          "callMethod.ClassNotFoundException",
414                                          null,
415                                          e,
416                                          this.className);
417             } catch (final NoSuchMethodException e) {
418                 RunLevel.LOG.error("class '" + this.className + "' does not own method '" + this.methodName + "'", e);
419                 throw new EFapsException(getClass(),
420                                          "callMethod.NoSuchMethodException",
421                                          null,
422                                          e,
423                                          this.className,
424                                          this.methodName);
425             } catch (final IllegalAccessException e) {
426                 RunLevel.LOG.error("could not access class '" + this.className + "' method '"
427                         + this.methodName + "'", e);
428                 throw new EFapsException(getClass(),
429                                          "callMethod.IllegalAccessException",
430                                          null,
431                                          e,
432                                          this.className,
433                                          this.methodName);
434             } catch (final InvocationTargetException e) {
435                 RunLevel.LOG.error("could not execute class '" + this.className + "' method '"
436                         + this.methodName + "' because an exception was thrown.", e);
437                 if (e.getCause() != null) {
438                     if (e.getCause() instanceof EFapsException) {
439                         throw (EFapsException) e.getCause();
440                     } else {
441                         throw new EFapsException(getClass(), "callMethod.InvocationTargetException",
442                                              null,
443                                              e.getCause(),
444                                              this.className,
445                                              this.methodName);
446                     }
447                 } else {
448                     throw new EFapsException(getClass(),
449                                              "callMethod.InvocationTargetException",
450                                              null,
451                                              e,
452                                              this.className,
453                                              this.methodName);
454                 }
455             }
456         }
457     }
458 }