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.dbproperty;
22  
23  import java.sql.PreparedStatement;
24  import java.sql.ResultSet;
25  import java.sql.SQLException;
26  import java.util.Formatter;
27  import java.util.HashSet;
28  import java.util.Locale;
29  import java.util.MissingFormatArgumentException;
30  import java.util.Set;
31  
32  import org.efaps.admin.EFapsSystemConfiguration;
33  import org.efaps.admin.KernelSettings;
34  import org.efaps.admin.common.SystemConfiguration;
35  import org.efaps.db.Context;
36  import org.efaps.db.transaction.ConnectionResource;
37  import org.efaps.db.wrapper.SQLPart;
38  import org.efaps.db.wrapper.SQLSelect;
39  import org.efaps.util.EFapsException;
40  import org.efaps.util.cache.CacheLogListener;
41  import org.efaps.util.cache.InfinispanCache;
42  import org.infinispan.Cache;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  /**
47   * This class reads the Properties for eFaps from the connected Database and
48   * holds them in a cache to be accessed fast during normal runtime. <br>
49   * The Keys will be read from the database in the order of the Sequence of a
50   * Bundle. That gives the possibility to override the key of a Bundle with the
51   * same key of another Bundle by using a higher Sequence.<br>
52   * The value returned for a key is searched first in the localized version, if
53   * no Value can be found or no localized version for this language is existing
54   * than the default value will be returned.
55   *
56   * @author The eFaps Team
57   * @version $Id$
58   */
59  public final class DBProperties
60  {
61  
62      /**
63       * Logger for this class.
64       */
65      private static final Logger LOG = LoggerFactory.getLogger(DBProperties.class);
66  
67      /**
68       * Name of the Infinispan Cache.
69       */
70      private static final String CACHENAME = DBProperties.class.getName();
71  
72      /**
73       * String used instead of a <code>NULL</code> value.
74       */
75      private static final String NULLVALUE = "org.efaps.admin.dbproperty.NULL";
76  
77      /**
78       * SQL Statement used to find a property including as criteria the language.
79       */
80      private static final String SQLSELECT = new SQLSelect()
81                      .column(0, "DEFAULTV")
82                      .column(2, "VALUE")
83                      .from("T_ADPROP", 0)
84                      .leftJoin("T_ADPROPBUN", 1, "ID", 0, "BUNDLEID")
85                      .leftJoin("T_ADPROPLOC", 2, "PROPID", 0, "ID")
86                      .leftJoin("T_ADLANG", 3, "ID", 2, "LANGID")
87                      .addPart(SQLPart.WHERE).addColumnPart(0, "PROPKEY").addPart(SQLPart.EQUAL).addValuePart("?")
88                      .addPart(SQLPart.AND)
89                      .addPart(SQLPart.PARENTHESIS_OPEN)
90                      .addColumnPart(3, "LANG").addPart(SQLPart.EQUAL).addValuePart("?")
91                      .addPart(SQLPart.OR)
92                      .addColumnPart(3, "LANG").addPart(SQLPart.IS).addPart(SQLPart.NULL)
93                      .addPart(SQLPart.PARENTHESIS_CLOSE)
94                      .addPart(SQLPart.ORDERBY)
95                      .addColumnPart(1, "SEQUENCE").addPart(SQLPart.DESC).addPart(SQLPart.COMMA)
96                      .addColumnPart(3, "LANG").toString();
97  
98      /**
99       * SQL Statement used to find a property used if with the previous Statement no
100      * result where found.
101      */
102     private static final String SQLSELECTDEF = new SQLSelect()
103                     .column(0, "DEFAULTV")
104                     .from("T_ADPROP", 0)
105                     .leftJoin("T_ADPROPBUN", 1, "ID", 0, "BUNDLEID")
106                     .addPart(SQLPart.WHERE).addColumnPart(0, "PROPKEY").addPart(SQLPart.EQUAL).addValuePart("?")
107                     .addPart(SQLPart.ORDERBY)
108                     .addColumnPart(1, "SEQUENCE").addPart(SQLPart.DESC).toString();
109 
110     /**
111      * SQL Statement used to get the properties that must be cached on start.
112      */
113     private static final String SQLSELECTONSTART = new SQLSelect()
114                     .column(0, "PROPKEY")
115                     .column(0, "DEFAULTV")
116                     .column(2, "VALUE")
117                     .column(3, "LANG")
118                     .from("T_ADPROP", 0)
119                     .innerJoin("T_ADPROPBUN", 1, "ID", 0, "BUNDLEID")
120                     .leftJoin("T_ADPROPLOC", 2, "PROPID", 0, "ID")
121                     .leftJoin("T_ADLANG", 3, "ID", 2, "LANGID")
122                     .addPart(SQLPart.WHERE)
123                     .addColumnPart(1, "CACHEONSTART").addPart(SQLPart.EQUAL).addBooleanValue(true)
124                     .addPart(SQLPart.ORDERBY)
125                     .addColumnPart(1, "SEQUENCE").addPart(SQLPart.ASC).addPart(SQLPart.COMMA)
126                     .addColumnPart(0, "PROPKEY").toString();
127 
128     /**
129      * SQL Statement to get a list of languages.
130      */
131     private static final String SQLLANG = new SQLSelect()
132                     .column("LANG")
133                     .from("T_ADLANG").toString();
134     /**
135      * Private Constructor for Utility class.
136      */
137     private DBProperties()
138     {
139     }
140 
141     /**
142      * Method to find out if a specified key is existing.<br>
143      * It is only checked in the default.
144      *
145      * @param _key Key to search for
146      * @return true if the key exists
147      */
148     public static boolean hasProperty(final String _key)
149     {
150         return DBProperties.getProperty(_key, false) != null;
151     }
152 
153     /**
154      * Method that returns the value, depending on the language of the Context,
155      * for the given key. <br>
156      * The Search for the key, first searches for a localized Version and if not
157      * found for a Default. If no value can be found, the key will be returned.
158      *
159      * @param _key Key to Search for
160      * @return if key exists, the value for the key, otherwise the key
161      */
162     public static String getProperty(final String _key)
163     {
164         String language = null;
165         try {
166             language = Context.getThreadContext().getLanguage();
167         } catch (final EFapsException e) {
168             DBProperties.LOG.error("not able to read the language from the context", e);
169         }
170         return DBProperties.getProperty(_key, language);
171     }
172 
173     /**
174      * Method that returns the value, depending on the language of the Context,
175      * for the given key. <br>
176      * The Search for the key, first searches for a localized Version and if not
177      * found for a Default. If no value can be found, the key will be returned.
178      *
179      * @param _key Key to Search for
180      * @param _returnKey return the key if no property found
181      * @return if key exists, the value for the key, otherwise the key
182      */
183     public static String getProperty(final String _key,
184                                      final boolean _returnKey)
185     {
186         String language = null;
187         try {
188             language = Context.getThreadContext().getLanguage();
189         } catch (final EFapsException e) {
190             DBProperties.LOG.error("not able to read the language from the context", e);
191         }
192         return DBProperties.getProperty(_key, language, _returnKey);
193     }
194 
195     /**
196      * Method that returns the value, depending on the parameter _language, for
197      * the given key. <br>
198      * The Search for the key, first searches for a localized Version and if not
199      * found for a Default. If no value can be found, the key will be returned.
200      *
201      * @param _key Key to Search for
202      * @param _language language to use
203      * @return if key exists, the value for the key, otherwise the key
204      */
205     public static String getProperty(final String _key,
206                                      final String _language)
207     {
208         return DBProperties.getProperty(_key, _language, true);
209     }
210 
211     /**
212      * Method that returns the value, depending on the parameter _language, for
213      * the given key. <br>
214      * The Search for the key, first searches for a localized Version and if not
215      * found for a Default. If no value can be found, the key will be returned.
216      *
217      * @param _key Key to Search for
218      * @param _language language to use
219      * @param _returnKey return the key if no property found
220      * @return if key exists, the value for the key, otherwise the key
221      */
222     public static String getProperty(final String _key,
223                                      final String _language,
224                                      final boolean _returnKey)
225     {
226         String value = null;
227         try {
228             final SystemConfiguration config = EFapsSystemConfiguration.get();
229             final boolean showKey = config == null
230                             ? false
231                             : config.getAttributeValueAsBoolean(KernelSettings.SHOW_DBPROPERTIES_KEY);
232 
233             if (showKey) {
234                 value = _key;
235             } else {
236                 final String cachKey = _language + ":" + _key;
237                 final Cache<String, String> cache = InfinispanCache.get().<String, String>getCache(
238                                 DBProperties.CACHENAME);
239                 if (cache.containsKey(cachKey)) {
240                     value = cache.get(cachKey);
241                     if (value.equals(DBProperties.NULLVALUE)) {
242                         value = null;
243                     }
244                 } else {
245                     value = DBProperties.getValueFromDB(_key, _language);
246                     if (value == null) {
247                         cache.put(cachKey, DBProperties.NULLVALUE);
248                     } else {
249                         cache.put(cachKey, value);
250                     }
251                 }
252             }
253         } catch (final EFapsException e) {
254             DBProperties.LOG.error("not able to read ShowDBPropertiesKey from the webConfig", e);
255         }
256         return (value == null && _returnKey) ? "?? - " + _key + " - ??" : value;
257     }
258 
259     /**
260      * Get a DBProperty and apply a <code>java.util.Formatter</code> with the
261      * given _args on it.
262      *
263      * @param _key key the DBProperty will be searched for
264      * @param _args object to be used for the formated
265      * @return formated value for the key
266      */
267     public static String getFormatedDBProperty(final String _key,
268                                                final Object... _args)
269     {
270         String language = null;
271         try {
272             language = Context.getThreadContext().getLanguage();
273         } catch (final EFapsException e) {
274             DBProperties.LOG.error("not able to read the language from the context", e);
275         }
276         return DBProperties.getFormatedDBProperty(_key, language, _args);
277     }
278 
279     /**
280      * Get a DBProperty and apply a <code>java.util.Formatter</code> with the
281      * given _args on it.
282      *
283      * @param _key key the DBProperty will be searched for
284      * @param _language language to be applied
285      * @param _args object to be used for the formated
286      * @return formated value for the key
287      */
288     public static String getFormatedDBProperty(final String _key,
289                                                final String _language,
290                                                final Object... _args)
291     {
292         String ret = "";
293         try {
294             ret = DBProperties.getProperty(_key, _language);
295             final Locale local = Context.getThreadContext().getLocale();
296             final Formatter formatter = new Formatter(local);
297             formatter.format(ret, _args);
298             ret = formatter.toString();
299             formatter.close();
300         } catch (final EFapsException e) {
301             DBProperties.LOG.error("not able to read the locale from the context", e);
302         } catch (final MissingFormatArgumentException e) {
303             DBProperties.LOG.error("wrong format", e);
304         }
305         return ret;
306     }
307 
308     /**
309      * This method is initializing the cache.
310      *
311      * @param _key key to be read.
312      * @param _language language to search for
313      * @return value from the database, null if not found
314      */
315     private static String getValueFromDB(final String _key,
316                                          final String _language)
317     {
318         String ret = null;
319         try {
320             boolean closeContext = false;
321             if (!Context.isThreadActive()) {
322                 Context.begin();
323                 closeContext = true;
324             }
325             final ConnectionResource con = Context.getThreadContext().getConnectionResource();
326             final PreparedStatement stmt = con.getConnection().prepareStatement(DBProperties.SQLSELECT);
327             stmt.setString(1, _key);
328             stmt.setString(2, _language);
329             final ResultSet resultset = stmt.executeQuery();
330             if (resultset.next()) {
331                 final String defaultValue = resultset.getString(1);
332                 final String value = resultset.getString(2);
333                 if (value != null) {
334                     ret = value.trim();
335                 } else if (defaultValue != null) {
336                     ret = defaultValue.trim();
337                 }
338             } else {
339                 final PreparedStatement stmt2 = con.getConnection().prepareStatement(DBProperties.SQLSELECTDEF);
340                 stmt2.setString(1, _key);
341                 final ResultSet resultset2 = stmt2.executeQuery();
342                 if (resultset2.next()) {
343                     final String defaultValue = resultset2.getString(1);
344                     if (defaultValue != null) {
345                         ret = defaultValue.trim();
346                     }
347                 }
348                 resultset2.close();
349                 stmt2.close();
350             }
351             resultset.close();
352             stmt.close();
353             con.commit();
354             if (closeContext) {
355                 Context.rollback();
356             }
357         } catch (final EFapsException e) {
358             DBProperties.LOG.error("initialiseCache()", e);
359         } catch (final SQLException e) {
360             DBProperties.LOG.error("initialiseCache()", e);
361         }
362         return ret;
363     }
364 
365     /**
366      * Load the properties that must be cached on start.
367      */
368     private static void cacheOnStart()
369     {
370         try {
371             boolean closeContext = false;
372             if (!Context.isThreadActive()) {
373                 Context.begin();
374                 closeContext = true;
375             }
376             final ConnectionResource con = Context.getThreadContext().getConnectionResource();
377             final PreparedStatement stmtLang = con.getConnection().prepareStatement(DBProperties.SQLLANG);
378             final ResultSet rsLang = stmtLang.executeQuery();
379             final Set<String> languages = new HashSet<String>();
380             while (rsLang.next()) {
381                 languages.add(rsLang.getString(1).trim());
382             }
383             rsLang.close();
384             stmtLang.close();
385             final Cache<String, String> cache = InfinispanCache.get().<String, String>getCache(
386                             DBProperties.CACHENAME);
387             final PreparedStatement stmt = con.getConnection().prepareStatement(DBProperties.SQLSELECTONSTART);
388             final ResultSet resultset = stmt.executeQuery();
389             while (resultset.next()) {
390                 final String propKey = resultset.getString(1).trim();
391                 final String defaultValue = resultset.getString(2);
392                 if (defaultValue != null) {
393                      final String value = defaultValue.trim();
394                      for (final String lang : languages) {
395                          final String cachKey = lang + ":" + propKey;
396                          if (!cache.containsKey(cachKey)) {
397                              cache.put(cachKey, value);
398                          }
399                      }
400                 }
401                 final String value = resultset.getString(3);
402                 final String lang = resultset.getString(4);
403                 if (value != null) {
404                     final String cachKey = lang.trim() + ":" + propKey;
405                     cache.put(cachKey, value.trim());
406                 }
407             }
408             resultset.close();
409             stmt.close();
410             con.commit();
411             if (closeContext) {
412                 Context.rollback();
413             }
414         } catch (final EFapsException e) {
415             DBProperties.LOG.error("initialiseCache()", e);
416         } catch (final SQLException e) {
417             DBProperties.LOG.error("initialiseCache()", e);
418         }
419     }
420 
421     /**
422      * Initialize the Cache be calling it. Used from runtime level.
423      */
424     public static void initialize()
425     {
426         if (InfinispanCache.get().exists(DBProperties.CACHENAME)) {
427             InfinispanCache.get().<String, String>getCache(DBProperties.CACHENAME).clear();
428         } else {
429             InfinispanCache.get().<String, String>getCache(DBProperties.CACHENAME)
430                             .addListener(new CacheLogListener(DBProperties.LOG));
431         }
432         DBProperties.cacheOnStart();
433     }
434 }