/*
* Weblounge: Web Content Management System
* Copyright (c) 2003 - 2011 The Weblounge Team
* http://entwinemedia.com/weblounge
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package ch.entwine.weblounge.cache.impl;
import ch.entwine.weblounge.common.impl.util.config.ConfigurationUtils;
import ch.entwine.weblounge.common.site.Site;
import ch.entwine.weblounge.common.url.PathUtils;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
/**
* This factory is listening for sites, while registering a cache configuration
* for each site that is targeted at the {@link CacheConfigurationFactory}.
* <p>
* When registered with the system using the pid
* <code>ch.entwine.weblounge.cache</code>, it will be used as the basis for
* configuration objects.
*/
public class CacheConfigurationFactory implements ManagedService {
/** Logger */
private static final Logger logger = LoggerFactory.getLogger(CacheConfigurationFactory.class);
/** Service pid, used to look up the service configuration */
public static final String SERVICE_PID = "ch.entwine.weblounge.cache";
/** Service configurations per site */
private Map<String, CacheConfiguration> configurations = new HashMap<String, CacheConfiguration>();
/** The sites */
private Map<String, Site> sites = new HashMap<String, Site>();
/** Reference to the configuration admin service */
private ConfigurationAdmin configurationAdmin = null;
/** The cache configuration */
private Dictionary<Object, Object> cacheConfiguration = null;
/**
* {@inheritDoc}
*
* @see org.osgi.service.cm.ManagedService#updated(java.util.Dictionary)
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public void updated(Dictionary properties) throws ConfigurationException {
cacheConfiguration = properties;
// Loop over all configurations and update them accordingly
for (Map.Entry<String, CacheConfiguration> entry : configurations.entrySet()) {
CacheConfiguration configHolder = entry.getValue();
Configuration config = entry.getValue().getConfiguration();
// If there is no configuration, then there is no configuration admin.
// Highly unlikely, since we are inside the updated() method, but you
// never know :-)
if (config == null)
continue;
boolean previouslyEnabled = configHolder.isEnabled();
boolean nowEnabled = !ConfigurationUtils.isFalse((String) properties.get(CacheServiceImpl.OPT_ENABLE));
try {
Dictionary configuration = createConfiguration(configHolder.getIdentifier(), configHolder.getName());
if (nowEnabled && !previouslyEnabled) {
config = configurationAdmin.createFactoryConfiguration(CacheServiceFactory.SERVICE_PID);
config.update(properties);
configHolder.setConfiguration(config);
} else if (nowEnabled) {
config.update(configuration);
} else if (previouslyEnabled) {
config.delete();
configHolder.setConfiguration(null);
}
} catch (IOException e) {
logger.error("Error updating cache configuration in persistent store", e);
}
}
}
/**
* Creates a configuration for the cache service associated with
* <code>site</code> and returns it.
* <p>
* The configuration is compiled by taking the base configuration that was
* earlier obtained using the configuration admin service and adding the site
* specific properties to it.
*
* @param id
* the site identifier
* @param name
* the site name
* @return the configuration
*/
private Dictionary<Object, Object> createConfiguration(String id, String name) {
Hashtable<Object, Object> configuration = new Hashtable<Object, Object>();
// Add the default properties
if (cacheConfiguration != null) {
for (Enumeration<Object> e = cacheConfiguration.keys(); e.hasMoreElements();) {
Object key = e.nextElement();
configuration.put(key, cacheConfiguration.get(key));
}
}
// Add everything that's site specific
configuration.put(CacheServiceImpl.OPT_ID, id);
configuration.put(CacheServiceImpl.OPT_NAME, name);
configuration.put(CacheServiceImpl.OPT_DISKSTORE_PATH, PathUtils.concat(System.getProperty("java.io.tmpdir"), "sites", id, "cache"));
return configuration;
}
/**
* Returns the configuration for the cache with the given identifier or
* <code>null</code> if no such cache was registered.
*
* @param id
* the cache identifier
* @return the cache configuration
*/
public CacheConfiguration getConfiguration(String id) {
for (CacheConfiguration config : configurations.values()) {
if (id.equals(config.getIdentifier()))
return config;
}
return null;
}
/**
* Returns an array of all registered cache configurations.
*
* @return the cache configurations
*/
public CacheConfiguration[] getConfigurations() {
return configurations.values().toArray(new CacheConfiguration[configurations.size()]);
}
/**
* Asks the factory to register the given configuration with the configuration
* admin service.
*
* @param configuration
* the cache configuration
* @throws IOException
* if registering the configuration failed
*/
public void enable(CacheConfiguration configuration) throws IOException {
if (!configurations.containsValue(configuration))
return;
if (!configuration.isEnabled()) {
Configuration c = configurationAdmin.createFactoryConfiguration(CacheServiceFactory.SERVICE_PID);
configuration.getProperties().put(CacheServiceImpl.OPT_ENABLE, Boolean.TRUE.toString());
c.update(configuration.getProperties());
configuration.setConfiguration(c);
}
}
/**
* Asks the factory to withdraw the given configuration from the configuration
* admin service.
*
* @param configuration
* the cache configuration
* @throws IOException
* if withdrawing the configuration failed
*/
public void disable(CacheConfiguration configuration) throws IOException {
if (!configurations.containsValue(configuration))
return;
if (configuration.isEnabled()) {
configuration.getConfiguration().delete();
configuration.setConfiguration(null);
}
}
/**
* Creates and publishes configurations for this site using the
* {@link ConfigurationAdmin}.
* <p>
* This method is called by the OSGi framework for every site that is
* registered in the service registry.
*
* @param site
* the site
* @throws IOException
* if access to the persistent store fails
*/
void addSite(Site site) throws IOException {
sites.put(site.getIdentifier(), site);
if (configurationAdmin == null)
return;
boolean enabled = !ConfigurationUtils.isFalse((String) cacheConfiguration.get(CacheServiceImpl.OPT_ENABLE));
CacheConfiguration configHolder = new CacheConfiguration(site.getIdentifier(), site.getName());
configHolder.setProperties(createConfiguration(site.getIdentifier(), site.getName()));
// Create the initial properties
Configuration configuration = null;
if (enabled) {
configuration = configurationAdmin.createFactoryConfiguration(CacheServiceFactory.SERVICE_PID, null);
configuration.update(configHolder.getProperties());
configHolder.setConfiguration(configuration);
}
configurations.put(site.getIdentifier(), configHolder);
}
/**
* Removes the associated service configuration from
* {@link ConfigurationAdmin}, so the cache service's
* {@link org.osgi.service.cm.ManagedServiceFactory#deleted(String)} gets
* called.
* <p>
* This method is called by the OSGi framework for every site that disappears
* from the service registry.
*
* @param site
* the site
* @throws IOException
* if access to the persistent store fails
*/
void removeSite(Site site) throws IOException {
sites.remove(site.getIdentifier());
// Delete the configuration
CacheConfiguration configHolder = configurations.remove(site.getIdentifier());
if (configHolder == null)
return;
Configuration config = configHolder.getConfiguration();
if (config != null)
config.delete();
}
/**
* Sets the reference to the OSGi farmework's configuration admin service.
* Once the service reference is obtained, we use it to load the default
* configuration for cache instances and store it for further use.
*
* @param configurationAdmin
* the configuration admin service
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
this.configurationAdmin = configurationAdmin;
// Try to get hold of the service configuration
try {
Configuration serviceConfig = configurationAdmin.getConfiguration(CacheConfigurationFactory.SERVICE_PID);
if (serviceConfig != null && serviceConfig.getProperties() != null) {
cacheConfiguration = serviceConfig.getProperties();
} else {
logger.debug("No customized cache configuration found");
cacheConfiguration = new Hashtable();
}
} catch (IOException e) {
logger.error("Error reading cache configuration from configuration admin service: " + e.getMessage());
}
// Process sites that appeared while there was no configuration admin
// service around
for (Site site : sites.values()) {
if (!configurations.containsKey(site.getIdentifier())) {
try {
addSite(site);
} catch (IOException e) {
logger.error("Error adding cache configuration to the configuration admin service: " + e.getMessage());
}
}
}
}
}