/*
* 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.contentrepository.impl;
import ch.entwine.weblounge.common.impl.util.config.ConfigurationUtils;
import ch.entwine.weblounge.common.repository.ContentRepository;
import ch.entwine.weblounge.common.repository.ContentRepositoryException;
import ch.entwine.weblounge.common.repository.ResourceSerializerService;
import ch.entwine.weblounge.common.search.SearchIndex;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.contentrepository.impl.fs.FileSystemContentRepository;
import org.apache.commons.lang.StringUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
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.osgi.service.cm.ManagedServiceFactory;
import org.osgi.service.component.ComponentContext;
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;
/**
* Service factory that will return a content repository for each configuration
* that is published to the {@link org.osgi.service.cm.ConfigurationAdmin}.
* <p>
* The following properties need to be present in order for a content repository
* instance to be started.
* <ul>
* <li><code>repository.id</code> - identifier that needs to match the site
* identifier</li>
* <li><code>repository.class</code> - class name of the repository
* implementation</li>
* </ul>
* <p>
* For additional configuration, take a look at the sample configuration that
* comes with Weblounge. When registered with the system using the pid
* <code>ch.entwine.weblounge.contentrepository</code>, it will be used as the
* basis for configuration objects.
*/
public class ContentRepositoryServiceFactory implements ManagedServiceFactory, ManagedService {
/** The factory's service pid */
static final String SERVICE_PID = "ch.entwine.weblounge.contentrepository.factory";
/** Name of the repository implementation option */
static final String OPT_TYPE = "contentrepository.type";
/** Default implementation of the content repository */
static final String DEFAULT_REPOSITORY_TYPE = FileSystemContentRepository.class.getName();
/** The logging facility */
private static final Logger logger = LoggerFactory.getLogger(ContentRepositoryServiceFactory.class);
/** Service registrations per configuration pid */
private final Map<String, ServiceRegistration> services = new HashMap<String, ServiceRegistration>();
/** Default content repository type */
private String repositoryType = null;
/** This service factory's bundle context */
private BundleContext bundleCtx = null;
/** The current environment */
private Environment environment = null;
/** Service managing available resource serializer */
private ResourceSerializerService serializer = null;
/** The search index */
private SearchIndex searchIndex = null;
/**
* Sets a reference to the service factory's component context.
* <p>
* This method is called from the OSGi context upon service creation.
*
* @param ctx
* the component context
*/
protected void activate(ComponentContext ctx) {
this.bundleCtx = ctx.getBundleContext();
// Configure the service
Dictionary<?, ?> configuration = loadConfiguration(SERVICE_PID);
if (configuration != null) {
try {
updated(configuration);
} catch (ConfigurationException e) {
logger.error("Error configuring content repository service factory", e);
}
}
}
/**
* {@inheritDoc}
*
* @see org.osgi.service.cm.ManagedServiceFactory#getName()
*/
public String getName() {
return "Content repository service factory";
}
/**
* {@inheritDoc}
*
* @see org.osgi.service.cm.ManagedService#updated(java.util.Dictionary)
*/
public void updated(Dictionary properties) throws ConfigurationException {
String repositoryType = (String) properties.get(OPT_TYPE);
if (StringUtils.isBlank(repositoryType))
repositoryType = DEFAULT_REPOSITORY_TYPE;
if (!StringUtils.trimToEmpty(this.repositoryType).equals(repositoryType)) {
logger.info("Default content repository implementation is {}", repositoryType);
this.repositoryType = repositoryType;
}
}
/**
* {@inheritDoc}
*
* @see org.osgi.service.cm.ManagedServiceFactory#updated(java.lang.String,
* java.util.Dictionary)
*/
public void updated(String pid, Dictionary properties)
throws ConfigurationException {
// is this an update to an existing service?
if (services.containsKey(pid)) {
ServiceRegistration registration = services.get(pid);
ManagedService service = (ManagedService) bundleCtx.getService(registration.getReference());
service.updated(properties);
}
// Create a new content repository service instance
else {
String className = (String) properties.get(OPT_TYPE);
if (StringUtils.isBlank(className)) {
className = repositoryType;
}
Class<ContentRepository> repositoryImplementation;
ContentRepository repository = null;
try {
repositoryImplementation = (Class<ContentRepository>) Class.forName(className);
repository = repositoryImplementation.newInstance();
repository.setEnvironment(environment);
repository.setSerializer(serializer);
repository.setSearchIndex(searchIndex);
// If this is a managed service, make sure it's configured properly
// before the site is connected
if (repository instanceof ManagedService) {
Dictionary<Object, Object> finalProperties = new Hashtable<Object, Object>();
// Add the default configuration according to the repository type
Dictionary<Object, Object> configuration = loadConfiguration(repository.getType());
if (configuration != null) {
for (Enumeration<Object> keys = configuration.keys(); keys.hasMoreElements();) {
Object key = keys.nextElement();
Object value = configuration.get(key);
if (value instanceof String)
value = ConfigurationUtils.processTemplate((String) value);
finalProperties.put(key, value);
}
}
// Overwrite the default configuration with what was passed in
for (Enumeration<Object> keys = properties.keys(); keys.hasMoreElements();) {
Object key = keys.nextElement();
Object value = properties.get(key);
if (value instanceof String)
value = ConfigurationUtils.processTemplate((String) value);
finalProperties.put(key, value);
}
// push the repository configuration
((ManagedService) repository).updated(finalProperties);
}
// Register the service
String serviceType = ContentRepository.class.getName();
properties.put("service.pid", pid);
services.put(pid, bundleCtx.registerService(serviceType, repository, properties));
} catch (ClassNotFoundException e) {
throw new ConfigurationException(OPT_TYPE, "Repository implementation class " + className + " not found", e);
} catch (InstantiationException e) {
throw new ConfigurationException(OPT_TYPE, "Error instantiating repository implementation class " + className + " not found", e);
} catch (IllegalAccessException e) {
throw new ConfigurationException(OPT_TYPE, "Error accessing repository implementation class " + className + " not found", e);
}
}
}
/**
* {@inheritDoc}
*
* @see org.osgi.service.cm.ManagedServiceFactory#deleted(java.lang.String)
*/
public void deleted(String pid) {
ServiceRegistration registration = services.remove(pid);
ContentRepository repository = (ContentRepository) bundleCtx.getService(registration.getReference());
try {
repository.disconnect();
} catch (ContentRepositoryException e) {
logger.warn("Error disconnecting repository {}: {}", repository, e.getMessage());
}
try {
registration.unregister();
} catch (IllegalStateException e) {
// Never mind, the service has been unregistered already
} catch (Throwable t) {
logger.error("Unregistering content repository failed: {}", t.getMessage());
}
}
/**
* Connects to the configuration admin service and asks for the configuration
* identified by <code>pid</code>. If the configuration exists, it's
* properties will be returned, <code>null</code> otherwise.
*
* @param pid
* the service pid
* @return the configuration properties
*/
@SuppressWarnings({ "cast" })
private Dictionary<Object, Object> loadConfiguration(String pid) {
if (StringUtils.isBlank(pid))
return null;
ServiceReference ref = bundleCtx.getServiceReference(ConfigurationAdmin.class.getName());
if (ref != null) {
ConfigurationAdmin configurationAdmin = (ConfigurationAdmin) bundleCtx.getService(ref);
Configuration config;
try {
config = configurationAdmin.getConfiguration(pid);
if (config != null)
return (Dictionary<Object, Object>) config.getProperties();
} catch (IOException e) {
logger.error("Error trying to look up content repository service factory configuration", e);
}
}
return null;
}
/**
* OSGi callback to set the current environment.
*
* @param environment
* the environment
*/
void setEnvironment(Environment environment) {
this.environment = environment;
}
/**
* OSGi callback to set the resource serializer service.
*
* @param serializer
* the serializer
*/
void setResourceSerializer(ResourceSerializerService serializer) {
this.serializer = serializer;
}
/**
* OSGi callback to set the search index.
*
* @param index
* the search index
*/
void setSearchIndex(SearchIndex index) {
this.searchIndex = index;
}
}