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.ejb.beans.configuration;
034:
035: import com.flexive.core.Database;
036: import static com.flexive.core.DatabaseConst.TBL_GLOBAL_CONFIG;
037: import com.flexive.core.configuration.GenericConfigurationImpl;
038: import com.flexive.ejb.mbeans.FxCache;
039: import com.flexive.shared.*;
040: import com.flexive.shared.cache.FxCacheException;
041: import com.flexive.shared.configuration.DBVendor;
042: import com.flexive.shared.configuration.DivisionData;
043: import com.flexive.shared.configuration.SystemParameters;
044: import com.flexive.shared.exceptions.FxApplicationException;
045: import com.flexive.shared.exceptions.FxLoadException;
046: import com.flexive.shared.exceptions.FxNoAccessException;
047: import com.flexive.shared.exceptions.FxUpdateException;
048: import com.flexive.shared.interfaces.GlobalConfigurationEngine;
049: import com.flexive.shared.interfaces.GlobalConfigurationEngineLocal;
050: import com.flexive.shared.mbeans.FxCacheMBean;
051: import com.flexive.shared.mbeans.MBeanHelper;
052: import org.apache.commons.logging.Log;
053: import org.apache.commons.logging.LogFactory;
054:
055: import javax.ejb.*;
056: import javax.management.InstanceAlreadyExistsException;
057: import javax.management.MBeanRegistrationException;
058: import javax.management.NotCompliantMBeanException;
059: import javax.management.ObjectName;
060: import javax.naming.Context;
061: import javax.naming.InitialContext;
062: import javax.naming.NamingException;
063: import javax.sql.DataSource;
064: import java.io.Serializable;
065: import java.security.NoSuchAlgorithmException;
066: import java.sql.Connection;
067: import java.sql.DatabaseMetaData;
068: import java.sql.PreparedStatement;
069: import java.sql.SQLException;
070: import java.util.*;
071:
072: /**
073: * Global configuration MBean.
074: *
075: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
076: */
077:
078: @TransactionManagement(TransactionManagementType.CONTAINER)
079: @Stateless(name="GlobalConfigurationEngine")
080: public class GlobalConfigurationEngineBean extends
081: GenericConfigurationImpl implements GlobalConfigurationEngine,
082: GlobalConfigurationEngineLocal {
083: /**
084: * Maximum number of cached domains per hit/miss cache
085: * This should be at least roughly equal to the number of configured
086: * domains since the miss cache will likely be thrashed otherwise.
087: */
088: private static final int MAX_CACHED_DOMAINS = 1000;
089:
090: /**
091: * Cache path for storing config parameters
092: */
093: private static final String CACHE_CONFIG = "/globalconfig/";
094: /**
095: * Cache path for storing other values
096: */
097: private static final String CACHE_BEAN = "/globalconfigMBean/";
098: /**
099: * Cache path suffix for storing division data
100: */
101: private static final String CACHE_DIVISIONS = "divisionData";
102:
103: private static final transient Log LOG = LogFactory
104: .getLog(GlobalConfigurationEngineBean.class);
105:
106: /**
107: * Cached local copy of divisions, must be cleared if the cache is cleared
108: */
109: private DivisionData[] divisions = null;
110: /**
111: * Cache for mapping domain names to division IDs. Cleared when the division cache is cleared.
112: */
113: private final HashMap<String, Integer> domainCache = new HashMap<String, Integer>(
114: MAX_CACHED_DOMAINS);
115: /**
116: * Simple cache stats (displayed on shutdown)
117: */
118: private SimpleCacheStats cacheStats = new SimpleCacheStats(
119: "Global get");
120:
121: /**
122: * {@inheritDoc}
123: */
124: public void create() throws Exception {
125: // System.out.println("************ Creating global config ***************");
126: }
127:
128: /**
129: * {@inheritDoc}
130: */
131: public void destroy() throws Exception {
132: System.out.println("Global config cache stats: ");
133: System.out.println(cacheStats.toString());
134: System.out.println();
135: }
136:
137: // implement Configuration methods
138:
139: /**
140: * {@inheritDoc}
141: */
142: @Override
143: protected Connection getConnection() throws SQLException {
144: return Database.getGlobalDbConnection();
145: }
146:
147: /**
148: * {@inheritDoc}
149: */
150: @Override
151: protected PreparedStatement getSelectStatement(Connection conn,
152: String path, String key) throws SQLException {
153: String sql = "SELECT cvalue FROM " + TBL_GLOBAL_CONFIG
154: + " WHERE cpath=? and ckey=?";
155: PreparedStatement stmt = conn.prepareStatement(sql);
156: stmt.setString(1, path);
157: stmt.setString(2, key);
158: return stmt;
159: }
160:
161: /**
162: * {@inheritDoc}
163: */
164: @Override
165: protected PreparedStatement getSelectStatement(Connection conn,
166: String path) throws SQLException {
167: String sql = "SELECT ckey, cvalue FROM " + TBL_GLOBAL_CONFIG
168: + " WHERE cpath=?";
169: PreparedStatement stmt = conn.prepareStatement(sql);
170: stmt.setString(1, path);
171: return stmt;
172: }
173:
174: /**
175: * {@inheritDoc}
176: */
177: @Override
178: protected PreparedStatement getUpdateStatement(Connection conn,
179: String path, String key, String value) throws SQLException,
180: FxNoAccessException {
181: if (!isAuthorized()) {
182: throw new FxNoAccessException(
183: "ex.configuration.update.perm.global");
184: }
185: String sql = "UPDATE " + TBL_GLOBAL_CONFIG
186: + " SET cvalue=? WHERE cpath=? AND ckey=?";
187: PreparedStatement stmt = conn.prepareStatement(sql);
188: stmt.setString(1, value);
189: stmt.setString(2, path);
190: stmt.setString(3, key);
191: return stmt;
192: }
193:
194: /**
195: * {@inheritDoc}
196: */
197: @Override
198: protected PreparedStatement getInsertStatement(Connection conn,
199: String path, String key, String value) throws SQLException,
200: FxNoAccessException {
201: if (!isAuthorized()) {
202: throw new FxNoAccessException(
203: "ex.configuration.update.perm.global");
204: }
205: String sql = "INSERT INTO " + TBL_GLOBAL_CONFIG
206: + "(cpath, ckey, cvalue) VALUES (?, ?, ?)";
207: PreparedStatement stmt = conn.prepareStatement(sql);
208: stmt.setString(1, path);
209: stmt.setString(2, key);
210: stmt.setString(3, value);
211: return stmt;
212: }
213:
214: /**
215: * {@inheritDoc}
216: */
217: @Override
218: protected PreparedStatement getDeleteStatement(Connection conn,
219: String path, String key) throws SQLException,
220: FxNoAccessException {
221: if (!isAuthorized()) {
222: throw new FxNoAccessException(
223: "ex.configuration.delete.perm.global");
224: }
225: String sql = "DELETE FROM " + TBL_GLOBAL_CONFIG
226: + " WHERE cpath=? "
227: + (key != null ? " AND ckey=?" : "");
228: PreparedStatement stmt = conn.prepareStatement(sql);
229: stmt.setString(1, path);
230: if (key != null) {
231: stmt.setString(2, key);
232: }
233: return stmt;
234: }
235:
236: /**
237: * {@inheritDoc}
238: */
239: @Override
240: protected String getCachePath(String path) {
241: // global parameters have no dynamic context
242: return CACHE_CONFIG + path;
243: }
244:
245: // add global configuration-specific methods
246:
247: /**
248: * {@inheritDoc}
249: */
250: @TransactionAttribute(TransactionAttributeType.REQUIRED)
251: public int[] getDivisionIds() throws FxApplicationException {
252: try {
253: // check cache
254: int[] cachedDivisionIds = (int[]) getCache(
255: getBeanPath(CACHE_DIVISIONS), "allDivisions");
256: if (cachedDivisionIds != null) {
257: return cachedDivisionIds;
258: }
259: } catch (FxCacheException e) {
260: LOG.error("Cache failure (ignored): " + e.getMessage(), e);
261: }
262:
263: // get list of all configured divisions
264: Map<String, String> domainMappings = getAll(SystemParameters.GLOBAL_DIVISIONS_DOMAINS);
265: int[] divisionIds = new int[domainMappings.keySet().size()];
266: int ctr = 0;
267: for (String divisionId : domainMappings.keySet()) {
268: divisionIds[ctr++] = Integer.parseInt(divisionId);
269: }
270: Arrays.sort(divisionIds);
271: putCache(getBeanPath(CACHE_DIVISIONS), "allDivisions",
272: divisionIds);
273: return divisionIds;
274: }
275:
276: /**
277: * {@inheritDoc}
278: */
279: @TransactionAttribute(TransactionAttributeType.REQUIRED)
280: public DivisionData[] getDivisions() throws FxApplicationException {
281: synchronized (this ) {
282: if (divisions != null) {
283: return FxArrayUtils.clone(divisions);
284: }
285: }
286: int[] divisionIds = getDivisionIds();
287: ArrayList<DivisionData> divisionList = new ArrayList<DivisionData>(
288: divisionIds.length);
289: for (int divisionId : divisionIds) {
290: try {
291: divisionList.add(getDivisionData(divisionId));
292: } catch (Exception e) {
293: LOG.error("Invalid division data (ignored): "
294: + e.getMessage());
295: }
296: }
297: synchronized (this ) {
298: divisions = divisionList
299: .toArray(new DivisionData[divisionList.size()]);
300: return divisions;
301: }
302: }
303:
304: /**
305: * {@inheritDoc}
306: */
307: @TransactionAttribute(TransactionAttributeType.REQUIRED)
308: public DivisionData getDivisionData(int division)
309: throws FxApplicationException {
310: try {
311: DivisionData data = (DivisionData) getCache(
312: getBeanPath(CACHE_DIVISIONS), "" + division);
313: if (data != null) {
314: return data;
315: }
316: } catch (FxCacheException e) {
317: LOG.error("Cache failure (ignored): " + e.getMessage(), e);
318: }
319: // get datasource
320: String dataSource = get(SystemParameters.GLOBAL_DATASOURCES, ""
321: + division);
322: String domainRegEx = get(
323: SystemParameters.GLOBAL_DIVISIONS_DOMAINS, ""
324: + division);
325: DivisionData data = createDivisionData(division, dataSource,
326: domainRegEx);
327: // put in cache
328: putCache(getBeanPath(CACHE_DIVISIONS), "" + division, data);
329: return data;
330: }
331:
332: /**
333: * {@inheritDoc}
334: */
335: @TransactionAttribute(TransactionAttributeType.REQUIRED)
336: public DivisionData createDivisionData(int divisionId,
337: String dataSource, String domainRegEx) {
338: String dbVendor = "unknown";
339: String dbVersion = "unknown";
340: boolean available = false;
341: Connection con = null;
342: try {
343: Context c = new InitialContext();
344: try {
345: con = ((DataSource) c.lookup(dataSource))
346: .getConnection();
347: } catch (NamingException e) {
348: //one more try in java: private namespace
349: con = ((DataSource) c.lookup("java:" + dataSource))
350: .getConnection();
351: dataSource = "java:" + dataSource;
352: }
353: DatabaseMetaData dbmd = con.getMetaData();
354: dbVendor = dbmd.getDatabaseProductName();
355: dbVersion = dbmd.getDatabaseProductVersion();
356: available = true;
357: } catch (NamingException e) {
358: LOG.error("Failed to get global datasource " + dataSource
359: + " (flagged inactive)");
360: } catch (SQLException e) {
361: if (LOG.isDebugEnabled()) {
362: LOG.debug("Failed to get database meta information: "
363: + e.getMessage(), e);
364: }
365: } finally {
366: Database.closeObjects(GlobalConfigurationEngineBean.class,
367: con, null);
368: }
369: return new DivisionData(divisionId, available, dataSource,
370: domainRegEx, DBVendor.getVendor(dbVendor), dbVersion);
371: }
372:
373: /**
374: * {@inheritDoc}
375: */
376: @TransactionAttribute(TransactionAttributeType.REQUIRED)
377: public int getDivisionId(String serverName)
378: throws FxApplicationException {
379: Integer cachedDivisionId = domainCache.get(serverName);
380: if (cachedDivisionId != null) {
381: return cachedDivisionId;
382: }
383: DivisionData[] divisionIds = getDivisions();
384: int divisionId = -1;
385: for (DivisionData division : divisionIds) {
386: if (division.isMatchingDomain(serverName)) {
387: divisionId = division.getId();
388: break;
389: }
390: }
391: synchronized (this ) {
392: if (domainCache.size() > MAX_CACHED_DOMAINS) {
393: domainCache.clear();
394: }
395: domainCache.put(serverName, divisionId);
396: }
397: return divisionId;
398: }
399:
400: /**
401: * {@inheritDoc}
402: */
403: @TransactionAttribute(TransactionAttributeType.REQUIRED)
404: public synchronized void saveDivisions(
405: List<? extends DivisionData> divisions)
406: throws FxApplicationException {
407: removeAll(SystemParameters.GLOBAL_DATASOURCES);
408: removeAll(SystemParameters.GLOBAL_DIVISIONS_DOMAINS);
409: clearDivisionCache();
410: for (DivisionData division : divisions) {
411: // remove the "java:" prefix that may have been appended on some containers, use the canonical JDBC string
412: final String storedDataSource = division.getDataSource()
413: .replaceFirst("^java:", "");
414: // store parameters
415: put(SystemParameters.GLOBAL_DATASOURCES, String
416: .valueOf(division.getId()), storedDataSource);
417: put(SystemParameters.GLOBAL_DIVISIONS_DOMAINS, String
418: .valueOf(division.getId()), division
419: .getDomainRegEx());
420: }
421: }
422:
423: /**
424: * {@inheritDoc}
425: */
426: @TransactionAttribute(TransactionAttributeType.REQUIRED)
427: public String getRootLogin() throws FxApplicationException {
428: return get(SystemParameters.GLOBAL_ROOT_LOGIN);
429: }
430:
431: /**
432: * {@inheritDoc}
433: */
434: @TransactionAttribute(TransactionAttributeType.REQUIRED)
435: public String getRootPassword() throws FxApplicationException {
436: return get(SystemParameters.GLOBAL_ROOT_PASSWORD);
437: }
438:
439: /**
440: * {@inheritDoc}
441: */
442: @TransactionAttribute(TransactionAttributeType.REQUIRED)
443: public boolean isMatchingRootPassword(String userPassword)
444: throws FxApplicationException {
445: String hashedPassword = getRootPassword();
446: return getHashedPassword(userPassword).matches(hashedPassword);
447: }
448:
449: /**
450: * {@inheritDoc}
451: */
452: @TransactionAttribute(TransactionAttributeType.REQUIRED)
453: public void setRootLogin(String value)
454: throws FxApplicationException {
455: put(SystemParameters.GLOBAL_ROOT_LOGIN, value);
456: }
457:
458: /**
459: * {@inheritDoc}
460: */
461: @TransactionAttribute(TransactionAttributeType.REQUIRED)
462: public void setRootPassword(String value)
463: throws FxApplicationException {
464: put(SystemParameters.GLOBAL_ROOT_PASSWORD,
465: getHashedPassword(value));
466: }
467:
468: /**
469: * {@inheritDoc}
470: */
471: @TransactionAttribute(TransactionAttributeType.REQUIRED)
472: public synchronized void clearDivisionCache() {
473: FxCacheMBean cache = CacheAdmin.getInstance();
474: try {
475: // clear local cache
476: divisions = null;
477: domainCache.clear();
478: // clear tree cache
479: cache.globalRemove(getBeanPath(CACHE_DIVISIONS));
480: } catch (FxCacheException e) {
481: LOG.error("Failed to clear cache: " + e.getMessage(), e);
482: }
483: }
484:
485: /**
486: * {@inheritDoc}
487: */
488: @TransactionAttribute(TransactionAttributeType.REQUIRED)
489: public void registerCacheMBean(ObjectName name)
490: throws MBeanRegistrationException,
491: NotCompliantMBeanException, InstanceAlreadyExistsException {
492: //TODO: some exception handling and cross checks for prev. registrations wouldn't hurt ...
493: //TODO: maybe create a system beans and move this method there
494: MBeanHelper.locateServer().registerMBean(new FxCache(), name);
495: }
496:
497: /**
498: * Get the complete cache path for miscellaneous internal paths.
499: *
500: * @param path cache path to be stored
501: * @return the complete cache path for miscellaneous internal paths.
502: */
503: private String getBeanPath(String path) {
504: return CACHE_BEAN + path;
505: }
506:
507: /**
508: * {@inheritDoc}
509: */
510: @Override
511: protected void putCache(String path, String key, Serializable value) {
512: // put parameter in the global (without division data) cache
513: try {
514: CacheAdmin.getInstance().globalPut(path, key, value);
515: } catch (FxCacheException e) {
516: LOG.error("Failed to update cache (ignored): "
517: + e.getMessage());
518: }
519: }
520:
521: /**
522: * {@inheritDoc}
523: */
524: @Override
525: protected void deleteCache(String path, String key) {
526: try {
527: CacheAdmin.getInstance().globalRemove(path, key);
528: } catch (FxCacheException e) {
529: LOG.error("Failed to update cache (ignored): "
530: + e.getMessage());
531: }
532: }
533:
534: /**
535: * {@inheritDoc}
536: */
537: @Override
538: protected void deleteCache(String path) {
539: try {
540: CacheAdmin.getInstance().globalRemove(path);
541: } catch (FxCacheException e) {
542: LOG.error("Failed to update cache (ignored): "
543: + e.getMessage());
544: }
545: }
546:
547: /**
548: * {@inheritDoc}
549: */
550: @Override
551: protected Serializable getCache(String path, String key)
552: throws FxCacheException {
553: FxCacheMBean cache = CacheAdmin.getInstance();
554: return (Serializable) cache.globalGet(path, key);
555: }
556:
557: /**
558: * {@inheritDoc}
559: */
560: @Override
561: protected void logCacheHit(String path, String key) {
562: cacheStats.addHit();
563: }
564:
565: /**
566: * {@inheritDoc}
567: */
568: @Override
569: protected void logCacheMiss(String path, String key) {
570: cacheStats.addMiss();
571: }
572:
573: /**
574: * Returns true if the calling user is authorized for manipulationg
575: * the global configuration.
576: *
577: * @return true if the calling user is authorized for manipulationg
578: * the global configuration.
579: */
580: private boolean isAuthorized() {
581: return FxContext.get().isGlobalAuthenticated();
582: }
583:
584: /**
585: * Compute the hashed password for the given input.
586: *
587: * @param userPassword the password to be hashed
588: * @return the hashed password for the given input.
589: */
590: private String getHashedPassword(String userPassword) {
591: return FxSharedUtils.hashPassword(31289, userPassword);
592: }
593:
594: }
|