package org.apache.slide.projector.engine;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.slide.projector.ConfigurableProcessor;
import org.apache.slide.projector.ConfigurationException;
import org.apache.slide.projector.Context;
import org.apache.slide.projector.EnvironmentConsumer;
import org.apache.slide.projector.ProcessException;
import org.apache.slide.projector.Processor;
import org.apache.slide.projector.Projector;
import org.apache.slide.projector.Result;
import org.apache.slide.projector.URI;
import org.apache.slide.projector.application.Application;
import org.apache.slide.projector.application.ApplicationListener;
import org.apache.slide.projector.descriptor.ParameterDescriptor;
import org.apache.slide.projector.descriptor.ProcessorDescriptor;
import org.apache.slide.projector.descriptor.ResultDescriptor;
import org.apache.slide.projector.descriptor.ValidationException;
import org.apache.slide.projector.i18n.DefaultMessage;
import org.apache.slide.projector.i18n.ErrorMessage;
import org.apache.slide.projector.processor.SimpleProcessor;
import org.apache.slide.projector.processor.process.Process;
import org.apache.slide.projector.value.AnyValue;
import org.apache.slide.projector.value.NullValue;
import org.apache.slide.projector.value.StreamableValue;
import org.apache.slide.projector.value.URIValue;
import org.apache.slide.projector.value.Value;
import org.apache.webdav.lib.Subscriber;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.AttributesImpl;
import de.zeigermann.xml.simpleImporter.DefaultSimpleImportHandler;
import de.zeigermann.xml.simpleImporter.SimpleImporter;
import de.zeigermann.xml.simpleImporter.SimplePath;
/**
* The ProcessorManager is responsible for loading processors and their
* configuration. Because every processor is located via its URI, it might be
* possible to handle different versions of the same processor.
* They are reloaded if an event occurs that indicates, that the class or configuration has changed
* or was updated.
*/
public class ProcessorManager implements ApplicationListener, Subscriber {
public final static URI BINARY = new URIValue("image");
public final static URI LOCALE_RESOLVER = new URIValue("localeResolver");
public final static URI URL = new URIValue("url");
public final static URI THREAD = new URIValue("thread");
private final static String CLASSES_DIR = "classes/";
private final static Logger logger = Logger.getLogger(ProcessorManager.class.getName());
private final static String PROCESSOR_CONFIG = "processors.xml";
private final static URI SMALL_DEFAULT_ICON = new URIValue("/files/contelligent/images/process.jpg");
private final static URI LARGE_DEFAULT_ICON = new URIValue("/files/contelligent/images/process.jpg");
private static ProcessorManager processorManager = new ProcessorManager();
private Map configurationToApplication = new HashMap(); // URI -> URI
private Map installedProcessors = new HashMap();
private Map processorMap = new HashMap(256); // URI -> Processor
private Map configuredProcessors = new HashMap(128);
private Map configurationListeners = new HashMap(128);
private Map processorDescriptors = new HashMap(256); // URI -> ProcessorDescriptor
private ProjectorClassLoader processorClassLoader = new ProjectorClassLoader(this.getClass().getClassLoader(), new URIValue(Projector.getProjectorDir()+CLASSES_DIR));
private ProcessorManager() {
}
public void install(String type, URI applicationUri, URI configurationUri) {
if ( type == Application.PROCESSORS ) {
install(applicationUri, configurationUri);
configurationToApplication.put(configurationUri, applicationUri);
}
}
public void uninstall(String type, URI applicationUri, URI configurationUri) {
if ( type == Application.PROCESSORS ) {
uninstall(applicationUri, configurationUri);
configurationToApplication.remove(configurationUri);
}
}
public void update(String type, URI applcationUri, URI configurationUri) {
if ( type == Application.PROCESSORS ) {
update(applcationUri, configurationUri);
}
}
private void install(URI applicationUri, URI configurationUri) {
logger.log(Level.FINE, "Installing processors '"+configurationUri+"'");
try {
InputStream configuration = ((StreamableValue)Projector.getRepository().getResource(configurationUri, Projector.getCredentials())).getInputStream();
SimpleImporter importer = new SimpleImporter();
List alreadyInstalledProcessors = (List)installedProcessors.get(configurationUri);
if ( alreadyInstalledProcessors == null ) alreadyInstalledProcessors = new ArrayList();
ConfigurationHandler handler = new ConfigurationHandler(applicationUri, alreadyInstalledProcessors);
importer.addSimpleImportHandler(handler);
importer.parse(new InputSource(configuration));
List removedProcessors = new ArrayList();
alreadyInstalledProcessors.addAll(handler.getAddedProcessors());
for ( Iterator i = handler.getRemovedProcessors().iterator(); i.hasNext(); ) {
URI removedProcessorUri = (URI)i.next();
uninstallProcessor(removedProcessorUri);
alreadyInstalledProcessors.remove(removedProcessorUri);
}
installedProcessors.put(configurationUri, alreadyInstalledProcessors);
Projector.getRepository().subscribe("Update", configurationUri, 0, this, Projector.getCredentials());
} catch (Exception exception) {
logger.log(Level.SEVERE, "Error while parsing configuration", exception);
}
}
public void uninstall(URI applicationUri, URI configurationUri) {
logger.log(Level.FINE, "Uninstalling processors '"+configurationUri+"'");
List processors = (List)installedProcessors.get(configurationUri);
for ( Iterator j = processors.iterator(); j.hasNext(); ) {
URI processorUri = (URI)j.next();
uninstallProcessor(processorUri);
}
installedProcessors.remove(configurationUri);
Projector.getRepository().unsubscribe(configurationUri, this, Projector.getCredentials());
}
private void uninstallProcessor(URI processorUri) {
processorMap.remove(processorUri);
ProcessorDescriptor processorDescriptor = getProcessorDescriptor(processorUri);
URI processorConfiguration = processorDescriptor.getConfiguration();
if ( processorConfiguration != null ) {
configuredProcessors.remove(processorConfiguration);
Subscriber subscriber = (Subscriber)configurationListeners.get(processorConfiguration);
Projector.getRepository().unsubscribe(processorConfiguration, subscriber, Projector.getCredentials());
configurationListeners.remove(processorConfiguration);
}
processorDescriptors.remove(processorUri);
logger.log(Level.FINE, "Removing processor: "+processorUri);
}
private void update(URI applicationUri, URI configurationUri) {
Projector.getRepository().unsubscribe(configurationUri, this, Projector.getCredentials());
install(applicationUri, configurationUri);
}
public static ProcessorManager getInstance() {
return processorManager;
}
public void notify(String uri, Map information) {
URI configurationUri = new URIValue(uri);
update((URI)configurationToApplication.get(configurationUri), configurationUri);
}
public Processor getProcessor(URI uri) throws ProcessException {
if ( processorMap.containsKey(uri) ) {
return (Processor)processorMap.get(uri);
}
logger.log(Level.SEVERE, "Requested processor with URI=" + uri+ " not found!");
throw new ProcessException(new ErrorMessage("processorNotFound", new Object[] {uri.toString()}));
}
public URI getURI(Processor processor) throws ProcessException {
for ( Iterator i = processorMap.entrySet().iterator(); i.hasNext(); ) {
Map.Entry entry = (Map.Entry)i.next();
if ( entry.getValue() == processor ) {
return ((URI)entry.getKey());
}
}
logger.log(Level.SEVERE, "Requested URI for processor=" + processor + " not found!");
throw new ProcessException(new ErrorMessage("processorNotFound", new Object[] {processor.toString()}));
}
public Result process(URI processorUri, Map parameters, Context context) throws Exception {
Processor processor = getProcessor(processorUri);
return process(processor, parameters, context);
}
public static Result process(Processor processor, Map parameters, Context context) throws Exception {
ParameterDescriptor[] parameterDescriptors = processor.getParameterDescriptors();
prepareValues(parameterDescriptors, parameters, context);
return processor.process(parameters, context);
}
public Value process(URI simpleProcessorUri, Object input, Context context) throws Exception {
return process(simpleProcessorUri, input, null, context);
}
public Value process(URI simpleProcessorUri, Object input, String resultKey, Context context) throws Exception {
Processor processor = getProcessor(simpleProcessorUri);
ParameterDescriptor[] parameterDescriptors = processor.getParameterDescriptors();
if ( parameterDescriptors.length > 1 && countRequiredParameters(parameterDescriptors) > 1 ) {
throw new ProcessException(new ErrorMessage("valueProcessorNeedsTooManyParametersException"));
}
if ( resultKey == null ) {
ResultDescriptor resultDescriptor = processor.getResultDescriptor();
if ( resultDescriptor.getResultEntryDescriptors().length == 0 ) {
throw new ProcessException(new ErrorMessage("parameterProcessingException", new String[] { simpleProcessorUri.toString() }));
} else {
resultKey = resultDescriptor.getResultEntryDescriptors()[0].getName();
}
}
if ( input == null && parameterDescriptors.length == 1 && parameterDescriptors[0].isRequired()) {
throw new ProcessException(new ErrorMessage("requiredParameterMissing", new String[]{parameterDescriptors[0].getName()}));
} else if ( input == null && parameterDescriptors.length > 1 ) {
ParameterDescriptor requiredParameter = getRequiredParameter(parameterDescriptors);
if ( requiredParameter != null ) throw new ProcessException(new ErrorMessage("requiredParameterMissing", new String[]{ requiredParameter.getName()}));
} else {
try {
// check for SimpleProcessor to avoid HashMap creation
if ( processor instanceof EnvironmentConsumer ) {
Process.checkRequirements((EnvironmentConsumer)processor, context);
}
if ( processor instanceof SimpleProcessor ) {
Value preparedValue = prepareValue(parameterDescriptors[0], input, context);
input = ((SimpleProcessor)processor).process(preparedValue, context);
} else {
Map parameters = new HashMap();
if ( parameterDescriptors.length == 1 ) {
parameters.put(parameterDescriptors[0].getName(), prepareValue(parameterDescriptors[0], input, context));
} else {
for ( int i = 0; i < parameterDescriptors.length; i++ ) {
if ( parameterDescriptors[i].isRequired() ) {
parameters.put(parameterDescriptors[i].getName(), prepareValue(parameterDescriptors[i], input, context));
} else {
parameters.put(parameterDescriptors[i].getName(), parameterDescriptors[i].getDefaultValue());
}
}
}
Result processorResult = processor.process(parameters, context);
input = processorResult.getResultEntries().get(resultKey);
}
} catch ( Exception exception ) {
throw new ProcessException(new ErrorMessage("parameterProcessingException", new String[] { simpleProcessorUri.toString(), exception.getMessage() }), exception);
}
}
return (Value)input;
}
public static void prepareValues(ParameterDescriptor[] parameterDescriptors, Map parameters, Context context) throws Exception {
for ( int i = 0; i < parameterDescriptors.length; i++ ) {
String parameterName = parameterDescriptors[i].getName();
Object parameterValue = parameters.get(parameterName);
Value preparedValue = prepareValue(parameterDescriptors[i], parameterValue, context);
parameters.put(parameterName, preparedValue);
}
}
public static Value prepareValue(ParameterDescriptor parameterDescriptor, Object value, Context context) throws Exception {
Value preparedValue;
if ( value instanceof AnyValue ) {
value = ((AnyValue)value).load(context);
}
if ( value == null || value instanceof NullValue ) {
if ( parameterDescriptor.isRequired() ) {
throw new ValidationException(new ErrorMessage("requiredParameterMissing", new String[] { parameterDescriptor.getName() }));
} else {
preparedValue = parameterDescriptor.getDefaultValue();
}
} else {
preparedValue = parameterDescriptor.getValueDescriptor().valueOf(value, context);
parameterDescriptor.getValueDescriptor().validate(preparedValue, context);
}
return preparedValue;
}
public ProcessorDescriptor getProcessorDescriptor(URI uri) {
return (ProcessorDescriptor)processorDescriptors.get(uri);
}
public ProcessorDescriptor getProcessorDescriptor(Processor processor) {
for ( Iterator i = processorDescriptors.values().iterator(); i.hasNext(); ) {
ProcessorDescriptor processorDescriptor = (ProcessorDescriptor)i.next();
if ( processorDescriptor.getProcessor() == processor ) return processorDescriptor;
}
return null;
}
private void registerProcessor(URI uri, Processor processor) {
}
private static int countRequiredParameters(ParameterDescriptor[] descriptors) {
int count = 0;
for ( int i = 0; i < descriptors.length; i++ ) {
if ( descriptors[i].isRequired() ) {
count++;
}
}
return count;
}
private static ParameterDescriptor getRequiredParameter(ParameterDescriptor[] descriptors) {
for ( int i = 0; i < descriptors.length; i++ ) {
if ( descriptors[i].isRequired() ) {
return descriptors[i];
}
}
return null;
}
public class ConfigurationHandler extends DefaultSimpleImportHandler {
URI applicationUri;
List addedProcessors = new ArrayList();
List removedProcessors = new ArrayList();
public ConfigurationHandler(URI applicationUri, List installedProcessors) {
this.applicationUri = applicationUri;
removedProcessors.addAll(installedProcessors);
}
public void startElement(SimplePath path, String name, AttributesImpl attributes, String leadingCDdata) {
if (path.matches("processor")) {
boolean modified = false, added = false;
URI uri = new URIValue(attributes.getValue("uri"));
removedProcessors.remove(uri);
String configURI = attributes.getValue("config-uri");
String processorName = attributes.getValue("name");
String descriptionKey = attributes.getValue("description");
String smallIcon = attributes.getValue("small-icon");
String largeIcon = attributes.getValue("large-icon");
String clazz = attributes.getValue("class");
String bookmark = attributes.getValue("bookmark");
Processor processor = (Processor)processorMap.get(uri);
ProcessorDescriptor processorDescriptor = (ProcessorDescriptor)processorDescriptors.get(uri);
try {
if ( processor == null ) {
processor = (Processor)processorClassLoader.loadClass(clazz).getConstructor(new Class[0]).newInstance(new Object[0]);
added = true;
modified = true;
}
if ( processorDescriptor == null ) {
processorDescriptor = new ProcessorDescriptor(uri);
modified = true;
}
if ( processorName == null ) processorName = uri.toString();
if ( processorDescriptor.getName() == null || !processorDescriptor.getName().equals(processorName) ) {
processorDescriptor.setName(processorName);
modified = true;
}
if ( descriptionKey == null ) {
descriptionKey = "processorManager/noProcessorDescriptionAvailable";
}
DefaultMessage description = null;
if ( processorDescriptor.getDescription() == null || !processorDescriptor.getDescription().getId().equals(descriptionKey) ) {
description = new DefaultMessage(descriptionKey);
processorDescriptor.setDescription(description);
modified = true;
}
if ( processorDescriptor.getProcessor() != processor ) {
processorDescriptor.setProcessor(processor);
modified = true;
}
URI smallIconUri = SMALL_DEFAULT_ICON, largeIconUri = LARGE_DEFAULT_ICON;
if ( smallIcon != null ) smallIconUri = new URIValue(smallIcon);
if ( largeIcon != null ) largeIconUri = new URIValue(largeIcon);
if ( processorDescriptor.getSmallIcon() == null || !processorDescriptor.getSmallIcon().equals(smallIconUri) ) {
processorDescriptor.setSmallIcon(smallIconUri);
modified = true;
}
if ( processorDescriptor.getLargeIcon() == null || !processorDescriptor.getLargeIcon().equals(largeIconUri) ) {
processorDescriptor.setLargeIcon(largeIconUri);
modified = true;
}
if ( bookmark != null ) {
boolean isBookmark = Boolean.valueOf(bookmark).booleanValue();
if ( processorDescriptor.isBookmark() != isBookmark ) {
processorDescriptor.setBookmark(isBookmark);
modified = true;
}
}
if ( processor instanceof ConfigurableProcessor && configURI != null ) {
if ( !configURI.startsWith("/") ) {
configURI = applicationUri.toString() + configURI;
}
URI configurationUri = new URIValue(configURI);
if ( processorDescriptor.getConfiguration() == null || !processorDescriptor.getConfiguration().equals(configurationUri) ) {
logger.log(Level.FINE, "Configuring processor with config-URI=" + configURI);
processorDescriptor.setConfiguration(configurationUri);
StreamableValue config = (StreamableValue)Projector.getRepository().getResource(configurationUri, Projector.getCredentials());
((ConfigurableProcessor)processor).configure(config);
// listen for configuration changes
Subscriber subscriber = new ConfigurationListener();
Projector.getRepository().subscribe("Update", configurationUri, 0, subscriber, Projector.getCredentials());
configurationListeners.put(configurationUri, subscriber);
configuredProcessors.put(configurationUri, processor);
modified = true;
}
}
if ( added ) {
logger.log(Level.FINE, "Adding processor with URI=" + uri);
addedProcessors.add(uri);
}
if ( modified ) {
logger.log(Level.FINE, "Updating processor with URI=" + uri);
processorMap.put(uri, processor);
processorDescriptors.put(uri, processorDescriptor);
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Processor " + clazz + " could not be created or configured!", e);
}
}
}
List getAddedProcessors() {
return addedProcessors;
}
List getRemovedProcessors() {
return removedProcessors;
}
}
class ConfigurationListener implements Subscriber {
public void notify(String uri, Map information) {
URI processorUri = new URIValue(uri);
Processor processor = (Processor)configuredProcessors.get(processorUri);
logger.log(Level.FINE, "Reloading processor with configuration URI='"+uri+"'");
try {
StreamableValue config = (StreamableValue)Projector.getRepository().getResource(processorUri, Projector.getCredentials());
((ConfigurableProcessor)processor).configure(config);
} catch ( IOException e ) {
logger.log(Level.SEVERE, "Configuration resource with URI='"+uri+"' could not be loaded!", e);
} catch ( ConfigurationException e ) {
logger.log(Level.SEVERE, "Processor with configuration URI='" + uri + "' could not be reloaded! Configuration resource might be invalid!", e);
}
}
}
class ConfiguredProcessor {
private String configUri;
private URI processorUri;
public ConfiguredProcessor(String configUri, URI processorUri) {
this.configUri = configUri;
this.processorUri = processorUri;
}
public String getConfigUri() {
return configUri;
}
public URI getProcessorUri() {
return processorUri;
}
}
}