/*******************************************************************************
* Copyright (c) 2010-2014 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.skalli.core.configuration;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.io.IOUtils;
import org.eclipse.skalli.commons.ThreadPool;
import org.eclipse.skalli.core.storage.FileStorageComponent;
import org.eclipse.skalli.core.xstream.CompositeEntityClassLoader;
import org.eclipse.skalli.services.BundleProperties;
import org.eclipse.skalli.services.configuration.ConfigSection;
import org.eclipse.skalli.services.configuration.ConfigurationService;
import org.eclipse.skalli.services.configuration.EventConfigUpdate;
import org.eclipse.skalli.services.event.EventService;
import org.eclipse.skalli.services.permit.Permits;
import org.eclipse.skalli.services.persistence.StorageService;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.XStreamException;
public class ConfigurationComponent implements ConfigurationService {
private static final Logger LOG = LoggerFactory.getLogger(ConfigurationComponent.class);
private static final Logger AUDIT_LOG = LoggerFactory.getLogger("audit"); //$NON-NLS-1$
private static final String CATEGORY_CUSTOMIZATION = "customization"; //$NON-NLS-1$
private EventService eventService;
private StorageService storageService;
private String storageServiceClassName;
private Map<String, ConfigSection<?>> byStorageKey =
new ConcurrentHashMap<String, ConfigSection<?>>();
private Map<Class<?>, ConfigSection<?>> byConfigClass =
new ConcurrentHashMap<Class<?>, ConfigSection<?>>();
private static Map<Class<?>, Object> configCache =
new ConcurrentHashMap<Class<?>, Object>();
public ConfigurationComponent() {
storageServiceClassName = BundleProperties.getProperty(
BundleProperties.PROPERTY_STORAGE_SERVICE, FileStorageComponent.class.getName());
}
// Constructor for testing purposes.
// Set the storage services to use with bindStorageService().
ConfigurationComponent(String storageServiceClassName) {
this.storageServiceClassName = storageServiceClassName;
}
protected void activate(ComponentContext context) {
LOG.info(MessageFormat.format("[ConfigurationService] {0} : activated", //$NON-NLS-1$
(String) context.getProperties().get(ComponentConstants.COMPONENT_NAME)));
}
protected void deactivate(ComponentContext context) {
LOG.info(MessageFormat.format("[ConfigurationService] {0} : deactivated", //$NON-NLS-1$
(String) context.getProperties().get(ComponentConstants.COMPONENT_NAME)));
}
protected void bindEventService(EventService eventService) {
this.eventService = eventService;
LOG.info(MessageFormat.format("bindEventService({0})", eventService)); //$NON-NLS-1$
}
protected void unbindEventService(EventService eventService) {
LOG.info(MessageFormat.format("unbindEventService({0})", eventService)); //$NON-NLS-1$
this.eventService = null;
}
protected void bindStorageService(StorageService storageService) {
if (storageServiceClassName.equals(storageService.getClass().getName())) {
this.storageService = storageService;
configCache.clear();
notifyCustomizationChanged(storageService);
LOG.info(MessageFormat.format("bindStorageService({0})", storageService)); //$NON-NLS-1$
}
}
protected void unbindStorageService(StorageService storageService) {
if (storageServiceClassName.equals(storageService.getClass().getName())) {
LOG.info(MessageFormat.format("unbindStorageService({0})", storageService)); //$NON-NLS-1$
this.storageService = null;
configCache.clear();
notifyCustomizationChanged(storageService);
}
}
protected void bindConfigSection(ConfigSection<?> configSection) {
byConfigClass.put(configSection.getConfigClass(), configSection);
byStorageKey.put(configSection.getStorageKey(), configSection);
configCache.remove(configSection.getConfigClass());
notifyCustomizationChanged(configSection);
LOG.info(MessageFormat.format("bindConfigSection({0})", configSection)); //$NON-NLS-1$
}
protected void unbindConfigSection(ConfigSection<?> configSection) {
LOG.info(MessageFormat.format("unbindConfigSection({0})", configSection)); //$NON-NLS-1$
byConfigClass.remove(configSection.getConfigClass());
byStorageKey.remove(configSection.getStorageKey());
configCache.remove(configSection.getConfigClass());
notifyCustomizationChanged(configSection);
}
ConfigSection<?> getConfigSection(Class<?> configClass) {
return byConfigClass.get(configClass);
}
ConfigSection<?> getConfigSection(String storageKey) {
return byStorageKey.get(storageKey);
}
@Override
public <T> void writeConfiguration(T configuration) {
if (storageService == null) {
LOG.error("Cannot store configurations: StorageService not available");
return;
}
if (configuration == null) {
// delete configuration?
return;
}
ConfigSection<?> configSection = byConfigClass.get(configuration.getClass());
if (configSection == null) {
LOG.error(MessageFormat.format(
"Cannot store configuration: No suitable configuration extension " + //$NON-NLS-1$
"for configurations of type ''{0}'' available", //$NON-NLS-1$
configuration.getClass()));
return;
}
String storageKey = configSection.getStorageKey();
Class<?> configurationClass = configuration.getClass();
String xml = getXStream(configurationClass).toXML(configuration);
InputStream is = null;
try {
is = new ByteArrayInputStream(xml.getBytes("UTF-8")); //$NON-NLS-1$
storageService.write(CATEGORY_CUSTOMIZATION, storageKey, is);
configCache.put(configurationClass, configuration);
if (eventService != null) {
fireEvent(configSection, configuration);
}
AUDIT_LOG.info(MessageFormat.format("Configuration ''{0}'' changed by user ''{1}''",
storageKey, Permits.getLoggedInUser()));
} catch (UnsupportedEncodingException e) {
// should never happen for UTF-8
throw new IllegalStateException(e);
} catch (IOException e) {
LOG.error(MessageFormat.format("Failed to store configuration ''{0}''", storageKey), e);
} finally {
IOUtils.closeQuietly(is);
}
};
@Override
public <T> T readConfiguration(Class<T> configurationClass) {
if (storageService == null) {
LOG.warn("Cannot load configurations: StorageService not available");
return null;
}
if (configurationClass == null) {
return null;
}
Object config = configCache.get(configurationClass);
if (config == null) {
ConfigSection<?> configSection = byConfigClass.get(configurationClass);
if (configSection == null) {
LOG.error(MessageFormat.format(
"Cannot retrieve configuration: No suitable configuration extension " + //$NON-NLS-1$
"for configurations of type ''{0}'' available", //$NON-NLS-1$
configurationClass));
return null;
}
config = readConfiguration(configSection.getStorageKey(), configurationClass);
}
return configurationClass.cast(config);
}
private Object readConfiguration(String storageKey, Class<?> configurationClass) {
Object config = null;
InputStream is = null;
try {
is = storageService.read(CATEGORY_CUSTOMIZATION, storageKey);
if (is == null) {
return null;
}
config = getXStream(configurationClass).fromXML(is);
} catch (XStreamException e) {
LOG.error(MessageFormat.format(
"Failed to unmarshal configuration ''{0}'' ",
storageKey), e);
} catch (IOException e) {
LOG.error(MessageFormat.format(
"Failed to retrieve configuration ''{0}''",
storageKey), e);
} finally {
IOUtils.closeQuietly(is);
}
return config;
}
private XStream getXStream(Class<?> customizationClass) {
XStream xstream = new XStream();
ClassLoader classLoader = customizationClass.getClassLoader();
if (classLoader != null) {
xstream.setClassLoader(new CompositeEntityClassLoader(Collections.singleton(classLoader)));
}
return xstream;
}
private void notifyCustomizationChanged(final StorageService storageService) {
ThreadPool.submit(new Runnable() {
@Override
public void run() {
if (eventService != null) {
List<String> storageKeys = Collections.emptyList();
try {
storageKeys = storageService.keys(CATEGORY_CUSTOMIZATION);
} catch (IOException e) {
LOG.error("Failed to retrieve configuration keys", e);
return;
}
for (String storageKey: storageKeys) {
ConfigSection<?> configSection = byStorageKey.get(storageKey);
if (configSection != null) {
fireEvent(configSection);
}
}
}
}
});
}
private void notifyCustomizationChanged(final ConfigSection<?> configSection) {
ThreadPool.submit(new Runnable() {
@Override
public void run() {
if (eventService != null && storageService != null) {
fireEvent(configSection);
}
}
});
}
private void fireEvent(ConfigSection<?> configSection) {
Object config = readConfiguration(configSection.getStorageKey(), configSection.getConfigClass());
fireEvent(configSection, config);
}
private void fireEvent(ConfigSection<?> configSection, Object config) {
eventService.fireEvent(new EventConfigUpdate(configSection.getConfigClass(), config));
LOG.info(MessageFormat.format("Event sent: Configuration ''{0}'' has changed",
configSection.getConfigClass().getSimpleName()));
}
}