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 }