/*
* $Id$
*
* Copyright 2006, The jCoderZ.org Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the jCoderZ.org Project nor the names of
* its contributors may be used to endorse or promote products
* derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jcoderz.commons.config;
import java.lang.ref.WeakReference;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jcoderz.commons.EntityNotFoundException;
/**
* This ConfigurationCache implementation is based on a ReadOnly EntityBean
* that stores its data within a database table.
*
*/
public final class ConfigurationCacheByDbReadOnlyImpl
implements ConfigurationCacheInterface
{
/** Name of this class. */
private static final String CLASSNAME
= ConfigurationCacheByDbReadOnlyImpl.class.getName();
/** Logger for this class. */
private static final Logger logger = Logger.getLogger(CLASSNAME);
/** The mutex object to synchronize the config keys map. */
private static final Object MUTEX_KEYS = new Object();
/** The mutex object to synchronize the services set. */
private static final Object MUTEX_SERVICES = new Object();
/**
* cache the configuration keys also, to prevent set creation for every
* {@link #getKeys()} call
*/
private Set mConfigurationKeys = null;
/**
* This Set contains the Services that have been registered to receive
* notifications when the cache has been changed.
* The Set contains objects that implement the ConfigurationListener
* interface.
*/
private final Set mRegisteredServices = new HashSet();
private boolean mIsInitialized;
private Exception mInitException;
/**
* Singleton has no public constructor
*
* @see ConfigurationCacheByDbReadOnlyImpl#current()
*/
private ConfigurationCacheByDbReadOnlyImpl ()
{
try
{
init();
mIsInitialized = true;
mInitException = null;
}
catch (Exception x)
{
mIsInitialized = false;
mInitException = x;
final ConfigurationInitializationFailedException sysEvt
= new ConfigurationInitializationFailedException(x);
sysEvt.log();
}
}
/**
* Returns a reference to the one and only object instance of this
* class. Use this method to access the object's methods.
*
* @return a reference to the one and only object instance of this class.
*/
public static ConfigurationCacheByDbReadOnlyImpl current ()
{
final ConfigurationCacheByDbReadOnlyImpl result
= ConfigurationCacheHolder.INSTANCE;
if (! result.mIsInitialized)
{
throw new ConfigurationInitializationFailedException(
result.mInitException);
}
return result;
}
/**
* Setup configuration cache as MBean and initialize cache.
*/
private void init ()
{
final String method = "init";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, method);
}
reloadCache();
if (logger.isLoggable(Level.FINER))
{
logger.exiting(CLASSNAME, method);
}
}
/**
* {@inheritDoc}
* Returns the String value set for the given configuration key.
* @param key the key to look up.
* @return the value as string.
* @throws ConfigurationValueNotFoundException if the key is not set or
* the resource was not found.
*/
public String getString (String key)
throws ConfigurationValueNotFoundException
{
final String methodName = "getString";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, methodName, key);
}
final String result;
try
{
// No caching in this class is used for the config entry,
// because EntityBean is defined with ReadOnly and timeout=0.
final ConfigEntity entity
= ConfigEntityHelper.findReadOnlyByPrimaryKey(key);
result = entity.getValue();
CfgLogMessage.ConfigurationValueRead.log(key, result);
}
catch (EntityNotFoundException e)
{
throw new ConfigurationValueNotFoundException(key, e);
}
catch (RemoteException re)
{
throw new ConfigurationValueNotFoundException(key, re);
}
if (logger.isLoggable(Level.FINER))
{
logger.exiting(CLASSNAME, methodName, result);
}
return result;
}
/** {@inheritDoc} */
public Set getKeys ()
{
final Set result;
synchronized (MUTEX_KEYS)
{
result = Collections.unmodifiableSet(mConfigurationKeys);
}
return result;
}
/** {@inheritDoc} */
public void addConfigurationListener (ConfigurationListener newListener)
{
synchronized (MUTEX_SERVICES)
{
mRegisteredServices.add(new WeakReference(newListener));
}
}
/**
* Reloads the internal configuration cache from resource bundle.
* If an exception occurs during, the old cached properties are
* returned.
*/
public void reloadCache ()
{
final String method = "reloadCache";
if (logger.isLoggable(Level.FINER))
{
logger.entering(CLASSNAME, method);
}
try
{
final Collection all = ConfigEntityHelper.findAllReadOnly();
final Set keys = new HashSet(all.size());
for (final Iterator iter = all.iterator(); iter.hasNext();)
{
final ConfigEntity element = (ConfigEntity) iter.next();
keys.add(element.getConfigKey());
}
// This block violates the j2ee spec.
synchronized (MUTEX_KEYS)
{
mConfigurationKeys = keys;
}
}
catch (Exception e)
{
final ConfigurationInitializationFailedException cife
= new ConfigurationInitializationFailedException(e);
// CHECKME: create special config cache reload exception
cife.addParameter("DETAIL",
"Exception while updating ConfigurationCache.");
throw cife;
}
notifyServices();
if (logger.isLoggable(Level.FINER))
{
logger.exiting(CLASSNAME, method);
}
}
/**
* Notifies all registered services about the cache update.
*/
private void notifyServices ()
{
final ConfigUpdateEvent event = new ConfigUpdateEvent(this,
ConfigUpdateEvent.CACHE_UPDATED);
final List alive_listeners = new ArrayList();
final List dead_listeners = new ArrayList();
synchronized (MUTEX_SERVICES)
{
final Iterator it = mRegisteredServices.iterator();
while (it.hasNext())
{
final WeakReference wk = (WeakReference) it.next();
final ConfigurationListener cl = (ConfigurationListener) wk.get();
if (cl == null)
{
dead_listeners.add(wk);
}
else
{
alive_listeners.add(cl);
}
}
mRegisteredServices.removeAll(dead_listeners);
}
for (int i = 0; i < alive_listeners.size(); i++)
{
((ConfigurationListener) alive_listeners.get(i))
.updateConfiguration(event);
}
}
/**
* Local helper class holding a member initialized by calling
* the private constructor of the singleton.
*/
private static final class ConfigurationCacheHolder
{
private static final ConfigurationCacheByDbReadOnlyImpl INSTANCE
= new ConfigurationCacheByDbReadOnlyImpl();
}
}