001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2008
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.core.configuration;
034:
035: import com.flexive.core.Database;
036: import com.flexive.shared.CacheAdmin;
037: import com.flexive.shared.cache.FxCacheException;
038: import com.flexive.shared.configuration.Parameter;
039: import com.flexive.shared.configuration.ParameterData;
040: import com.flexive.shared.exceptions.*;
041: import com.flexive.shared.interfaces.GenericConfigurationEngine;
042: import org.apache.commons.logging.Log;
043: import org.apache.commons.logging.LogFactory;
044: import org.apache.commons.lang.SerializationUtils;
045:
046: import javax.ejb.TransactionAttribute;
047: import javax.ejb.TransactionAttributeType;
048: import java.io.Serializable;
049: import java.sql.Connection;
050: import java.sql.PreparedStatement;
051: import java.sql.ResultSet;
052: import java.sql.SQLException;
053: import java.util.Collection;
054: import java.util.HashMap;
055: import java.util.Map;
056:
057: /**
058: * Abstract base class for configuration methods. Implements templated getter/setter
059: * methods for configuration classes that may add custom behavior like caching.
060: * <p>
061: * An implementor must create SQL statements for reading, updating and deleting
062: * parameters, and a method for obtaining a database connection for the
063: * configuration table.
064: * </p>
065: * <p>
066: * The <code>setParameter/getParameter</code> methods may be overridden
067: * to implement custom behavior, e.g. caching of parameter values. Some
068: * set- and get-methods are declared final because they are wrappers
069: * for the (non-final) main set/get method.
070: * </p>
071: *
072: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
073: */
074:
075: public abstract class GenericConfigurationImpl implements
076: GenericConfigurationEngine {
077: private static final transient Log LOG = LogFactory
078: .getLog(GenericConfigurationImpl.class);
079:
080: /**
081: * Helper class to place unset parameters in the configuration cache.
082: */
083: protected static class UnsetParameter implements Serializable {
084: private static final long serialVersionUID = -289868789320707584L;
085: }
086:
087: /**
088: * Helper class to represent null value in the cache (since an unset
089: * parameter would also be null).
090: */
091: protected static class NullParameter implements Serializable {
092: private static final long serialVersionUID = -3959509316291057862L;
093: }
094:
095: /**
096: * Return a (new or existing) Connection to the configuration table.
097: *
098: * @throws SQLException if the connection could not be retrieved
099: * @return a Connection to the configuration table.
100: */
101: protected abstract Connection getConnection() throws SQLException;
102:
103: /**
104: * Return a select statement that selects the given path and key
105: * and returns the stored value.
106: * <p>
107: * Required SELECT arguments:
108: * <ol>
109: * <li>value</li>
110: * </ol>
111: * </p>
112: *
113: * @param conn the current connection
114: * @param path the requested path
115: * @param key the requested key
116: * @throws SQLException if a database error occurs
117: * @return a PreparedStatement selecting the value for the given path/key combination
118: */
119: protected abstract PreparedStatement getSelectStatement(
120: Connection conn, String path, String key)
121: throws SQLException;
122:
123: /**
124: * Return a select statement that selects all keys and values for the given path.
125: * <p>
126: * Required SELECT arguments:
127: * <ol>
128: * <li>key</li>
129: * <li>value</li>
130: * </ol>
131: * </p>
132: *
133: * @param conn the current connection
134: * @param path the requested path
135: * @throws SQLException if a database error occurs
136: * @return a PreparedStatement selecting all keys and values for the given path
137: */
138: protected abstract PreparedStatement getSelectStatement(
139: Connection conn, String path) throws SQLException;
140:
141: /**
142: * Return an update statement that updates the value for the given
143: * path/key combination.
144: *
145: * @param conn the current connection
146: * @param path path of the parameter
147: * @param key key to be updated
148: * @param value the new value to be stored
149: * @throws SQLException if a database error occurs
150: * @throws FxNoAccessException if the caller is not permitted to update the given parameter
151: * @return a PreparedStatement updating the given row
152: */
153: protected abstract PreparedStatement getUpdateStatement(
154: Connection conn, String path, String key, String value)
155: throws SQLException, FxNoAccessException;
156:
157: /**
158: * Return an insert statement that inserts a new row for the given
159: * path, key and value.
160: *
161: * @param conn the current connection
162: * @param path path of the new row
163: * @param key key of the new row
164: * @param value value of the new row
165: * @throws SQLException if a database error occurs
166: * @throws FxNoAccessException if the caller is not permitted to create the given parameter
167: * @return a PreparedStatement for inserting the given path/key/value
168: */
169: protected abstract PreparedStatement getInsertStatement(
170: Connection conn, String path, String key, String value)
171: throws SQLException, FxNoAccessException;
172:
173: /**
174: * Return a delete statement to delete the given parameter.
175: *
176: * @param conn the current connection
177: * @param path path of the row to be deleted
178: * @param key key of the row to be deleted. If null, all keys under the given path should be deleted.
179: * @return a PreparedStatement for deleting the given path/key
180: * @throws SQLException if a database error occurs
181: * @throws FxNoAccessException if the caller is not permitted to delete the given parameter
182: */
183: protected abstract PreparedStatement getDeleteStatement(
184: Connection conn, String path, String key)
185: throws SQLException, FxNoAccessException;
186:
187: /**
188: * Return the cache path for the given configuration parameter path.
189: * If this method returns null (like the default implementation), caching
190: * is disabled. Be aware that you have to add the context to your cache path,
191: * e.g. the user ID for user settings.
192: *
193: * @param path the parameter path to be mapped
194: * @return the mapped parameter path, or null to disable caching
195: */
196: protected String getCachePath(String path) {
197: return null;
198: }
199:
200: /**
201: * Wrapper for simple cache stats. May be used as hook
202: * for adding cache logging or as an aspectj pointcut.
203: *
204: * @param path the parameter path that caused the cache hit
205: * @param key the parameter key that caused the cache hit
206: */
207: protected void logCacheHit(String path, String key) {
208: // no cache stats by default
209: }
210:
211: /**
212: * Wrapper for simple cache stats. May be used as hook
213: * for adding cache logging or as an aspectj pointcut.
214: *
215: * @param path the parameter path that caused the cache hit
216: * @param key the parameter key that caused the cache hit
217: */
218: protected void logCacheMiss(String path, String key) {
219: // no cache stats by default
220: }
221:
222: /**
223: * {@inheritDoc}
224: */
225: @TransactionAttribute(TransactionAttributeType.REQUIRED)
226: public <T extends Serializable> void put(Parameter<T> parameter,
227: String key, T value) throws FxApplicationException {
228:
229: if (!parameter.isValid(value)) {
230: throw new FxUpdateException(
231: "ex.configuration.parameter.value", parameter,
232: value);
233: }
234:
235: // put into DB config table
236: Connection conn = null;
237: PreparedStatement stmt = null;
238: ParameterData<T> data = parameter.getData();
239: try {
240: conn = getConnection();
241: stmt = getSelectStatement(conn, data.getPath().getValue(),
242: key);
243: ResultSet rs = stmt.executeQuery();
244: boolean valueExists = rs.next();
245: stmt.close();
246: stmt = null;
247: if (valueExists) {
248: // update existing record
249: stmt = getUpdateStatement(conn, data.getPath()
250: .getValue(), key, value != null ? parameter
251: .getDatabaseValue(value) : null);
252: } else {
253: // create new record
254: stmt = getInsertStatement(conn, data.getPath()
255: .getValue(), key, value != null ? parameter
256: .getDatabaseValue(value) : null);
257: }
258: stmt.executeUpdate();
259:
260: // update cache?
261: String cachePath = getCachePath(data.getPath().getValue());
262: if (cachePath != null) {
263: putCache(
264: cachePath,
265: key,
266: value != null ? (Serializable) SerializationUtils
267: .clone(value)
268: : new NullParameter());
269: }
270: } catch (SQLException se) {
271: FxUpdateException ue = new FxUpdateException(LOG, se,
272: "ex.db.sqlError", se.getMessage());
273: LOG.error(ue, se);
274: throw ue;
275: } finally {
276: Database.closeObjects(GenericConfigurationImpl.class, conn,
277: stmt);
278: }
279: }
280:
281: /**
282: * {@inheritDoc}
283: */
284: @TransactionAttribute(TransactionAttributeType.REQUIRED)
285: public final <T extends Serializable> void put(
286: Parameter<T> parameter, T value)
287: throws FxApplicationException {
288: put(parameter, parameter.getData().getKey(), value);
289: }
290:
291: /**
292: * Get a configuration parameter identified by a path and a key.
293: *
294: * @param parameter the actual parameter instance
295: * @param path path of the parameter
296: * @param key key of the parameter
297: * @return the parameter value
298: * @throws FxLoadException if the parameter could not be loaded
299: * @throws FxNotFoundException if the parameter does not exist
300: */
301: protected <T extends Serializable> Object getParameter(
302: Parameter<T> parameter, String path, String key)
303: throws FxLoadException, FxNotFoundException {
304: String cachePath = getCachePath(path);
305: if (cachePath != null) {
306: // try cache first
307: try {
308: Object value = getCache(cachePath, key);
309: if (value != null) {
310: logCacheHit(path, key);
311: if (value instanceof UnsetParameter) {
312: // check for null object
313: throw new FxNotFoundException(
314: "ex.configuration.parameter.notfound",
315: path, key);
316: } else if (value instanceof NullParameter) {
317: return null;
318: } else {
319: return value;
320: }
321: }
322: } catch (FxCacheException e) {
323: LOG.error("Cache failure (ignored): " + e.getMessage(),
324: e);
325: }
326: }
327: // load parameter from config table
328: logCacheMiss(path, key);
329: Serializable value = loadParameterFromDb(path, key);
330: if (cachePath != null) {
331: // add value to cache
332: putCache(cachePath, key, parameter.getValue(value));
333: }
334: return value;
335: }
336:
337: /**
338: * {@inheritDoc}
339: */
340: @TransactionAttribute(TransactionAttributeType.REQUIRED)
341: public final <T extends Serializable> T get(Parameter<T> parameter)
342: throws FxApplicationException {
343: return get(parameter, parameter.getData().getKey());
344: }
345:
346: /**
347: * {@inheritDoc}
348: */
349: @TransactionAttribute(TransactionAttributeType.REQUIRED)
350: public final <T extends Serializable> T get(Parameter<T> parameter,
351: String key) throws FxApplicationException {
352: try {
353: return parameter.getValue(getParameter(parameter, parameter
354: .getPath().getValue(), key));
355: } catch (FxNotFoundException e) {
356: if (parameter.getDefaultValue() != null) {
357: return parameter.getDefaultValue();
358: } else {
359: throw e;
360: }
361: }
362: }
363:
364: /**
365: * {@inheritDoc}
366: */
367: @TransactionAttribute(TransactionAttributeType.REQUIRED)
368: public final <T extends Serializable> T get(Parameter<T> parameter,
369: String key, boolean ignoreDefault)
370: throws FxApplicationException {
371: try {
372: return parameter.getValue(getParameter(parameter, parameter
373: .getPath().getValue(), key));
374: } catch (FxNotFoundException e) {
375: if (!ignoreDefault && parameter.getDefaultValue() != null) {
376: return parameter.getDefaultValue();
377: } else {
378: throw e;
379: }
380: }
381: }
382:
383: /**
384: * {@inheritDoc}
385: */
386: @TransactionAttribute(TransactionAttributeType.REQUIRED)
387: public final <T extends Serializable> Map<String, T> getAll(
388: Parameter<T> parameter) throws FxApplicationException {
389: Connection conn = null;
390: PreparedStatement stmt = null;
391: ParameterData<T> data = parameter.getData();
392: HashMap<String, T> parameters = new HashMap<String, T>();
393: try {
394: conn = getConnection();
395: stmt = getSelectStatement(conn, data.getPath().getValue());
396: ResultSet rs = stmt.executeQuery();
397: while (rs != null && rs.next()) {
398: // retrieve parameters and put them in hashmap
399: parameters.put(rs.getString(1), parameter.getValue(rs
400: .getObject(2)));
401: }
402: return parameters;
403: } catch (SQLException se) {
404: throw new FxLoadException(LOG, se, "ex.db.sqlError", se
405: .getMessage());
406: } finally {
407: Database.closeObjects(GenericConfigurationImpl.class, conn,
408: stmt);
409: }
410: }
411:
412: /**
413: * {@inheritDoc}
414: */
415: @TransactionAttribute(TransactionAttributeType.REQUIRED)
416: public final <T extends Serializable> Collection<String> getKeys(
417: Parameter<T> parameter) throws FxApplicationException {
418: return getAll(parameter).keySet();
419: }
420:
421: /**
422: * {@inheritDoc}
423: */
424: @TransactionAttribute(TransactionAttributeType.REQUIRED)
425: public <T extends Serializable> void remove(Parameter<T> parameter,
426: String key) throws FxApplicationException {
427: Connection conn = null;
428: PreparedStatement stmt = null;
429: try {
430: conn = getConnection();
431: stmt = getDeleteStatement(conn, parameter.getPath()
432: .getValue(), key);
433: stmt.executeUpdate();
434: String cachePath = getCachePath(parameter.getPath()
435: .getValue());
436: if (cachePath != null) {
437: // also remove from cache
438: if (key == null) {
439: // clear entire cache path
440: deleteCache(cachePath);
441: } else {
442: // clear single value
443: deleteCache(cachePath, key);
444: }
445: }
446: } catch (SQLException e) {
447: throw new FxRemoveException(LOG, e, "ex.db.sqlError", e
448: .getMessage());
449: } finally {
450: Database.closeObjects(GenericConfigurationImpl.class, conn,
451: stmt);
452: }
453: }
454:
455: /**
456: * {@inheritDoc}
457: */
458: @TransactionAttribute(TransactionAttributeType.REQUIRED)
459: public final <T extends Serializable> void remove(
460: Parameter<T> parameter) throws FxApplicationException {
461: remove(parameter, parameter.getKey());
462: }
463:
464: /**
465: * {@inheritDoc}
466: */
467: @TransactionAttribute(TransactionAttributeType.REQUIRED)
468: public final <T extends Serializable> void removeAll(
469: Parameter<T> parameter) throws FxApplicationException {
470: remove(parameter, null);
471: }
472:
473: /**
474: * Loads the given parameter from the database. Helper method for implementors.
475: *
476: * @param parameter the parameter to be loaded
477: * @param <T> value type of the parameter
478: * @return the parameter value
479: * @throws FxNotFoundException if the parameter does not exist
480: * @throws FxLoadException if the parameter could not be loaded
481: */
482: protected <T> T loadParameterFromDb(Parameter<T> parameter)
483: throws FxNotFoundException, FxLoadException {
484: return parameter.getValue(loadParameterFromDb(parameter
485: .getPath().getValue(), parameter.getData().getKey()));
486: }
487:
488: /**
489: * Loads the given parameter from the database. Helper method for implementors.
490: *
491: * @param path path of the parameter
492: * @param key key of the parameter
493: * @return the parameter value
494: * @throws FxLoadException if the parameter could not be loaded
495: * @throws FxNotFoundException if the parameter does not exist
496: */
497: protected Serializable loadParameterFromDb(String path, String key)
498: throws FxLoadException, FxNotFoundException {
499: // get from DB
500: Connection conn = null;
501: PreparedStatement stmt = null;
502: try {
503: conn = getConnection();
504: stmt = getSelectStatement(conn, path, key);
505: ResultSet rs = stmt.executeQuery();
506: if (rs != null && rs.next()) {
507: return (Serializable) rs.getObject(1);
508: } else {
509: String cachePath = getCachePath(path);
510: if (cachePath != null) {
511: // store null object in cache to avoid hitting the DB every time
512: putCache(cachePath, key, new UnsetParameter());
513: }
514: throw new FxNotFoundException(
515: "ex.configuration.parameter.notfound", path,
516: key);
517: }
518: } catch (SQLException se) {
519: throw new FxLoadException(LOG, se, "ex.db.sqlError", se
520: .getMessage());
521: } finally {
522: Database.closeObjects(GenericConfigurationImpl.class, conn,
523: stmt);
524: }
525: }
526:
527: /**
528: * Store the given value in the cache.
529: *
530: * @param path the parameter path
531: * @param key the parameter key
532: * @param value the serializable value to be stored
533: */
534: protected void putCache(String path, String key, Serializable value) {
535: try {
536: CacheAdmin.getInstance().put(path, key, value);
537: } catch (FxCacheException e) {
538: LOG.error("Failed to update cache (ignored): "
539: + e.getMessage());
540: }
541: }
542:
543: /**
544: * Delete the given parameter from the cache
545: *
546: * @param path path of the parameter to be removed
547: * @param key key of the parameter to be removed
548: */
549: protected void deleteCache(String path, String key) {
550: try {
551: CacheAdmin.getInstance().remove(path, key);
552: } catch (FxCacheException e) {
553: LOG.error("Failed to update cache (ignored): "
554: + e.getMessage());
555: }
556: }
557:
558: /**
559: * Delete the given path from the cache
560: *
561: * @param path the path to be removed
562: */
563: protected void deleteCache(String path) {
564: try {
565: CacheAdmin.getInstance().remove(path);
566: } catch (FxCacheException e) {
567: LOG.error("Failed to update cache (ignored): "
568: + e.getMessage());
569: }
570: }
571:
572: /**
573: * Returns the cached value of the given parameter
574: *
575: * @param path the parameter path
576: * @param key the parameter key
577: * @return the cached value of the given parameter
578: * @throws FxCacheException if a cache exception occured
579: */
580: protected Serializable getCache(String path, String key)
581: throws FxCacheException {
582: return (Serializable) SerializationUtils
583: .clone((Serializable) CacheAdmin.getInstance().get(
584: path, key));
585: }
586: }
|