/* ****************************************************************************
* CIShell: Cyberinfrastructure Shell, An Algorithm Integration Framework.
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Apache License v2.0 which accompanies
* this distribution, and is available at:
* http://www.apache.org/licenses/LICENSE-2.0.html
*
* Created on Aug 22, 2006 at Indiana University.
*
* Contributors:
* Indiana University -
* ***************************************************************************/
package org.cishell.reference.gui.menumanager.menu;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import org.cishell.app.service.datamanager.DataManagerService;
import org.cishell.framework.CIShellContext;
import org.cishell.framework.CIShellContextDelegate;
import org.cishell.framework.algorithm.Algorithm;
import org.cishell.framework.algorithm.AlgorithmCanceledException;
import org.cishell.framework.algorithm.AlgorithmCreationCanceledException;
import org.cishell.framework.algorithm.AlgorithmCreationFailedException;
import org.cishell.framework.algorithm.AlgorithmExecutionException;
import org.cishell.framework.algorithm.AlgorithmFactory;
import org.cishell.framework.algorithm.AlgorithmProperty;
import org.cishell.framework.algorithm.AllParametersMutatedOutException;
import org.cishell.framework.algorithm.DataValidator;
import org.cishell.framework.algorithm.ParameterMutator;
import org.cishell.framework.algorithm.ProgressMonitor;
import org.cishell.framework.algorithm.ProgressTrackable;
import org.cishell.framework.data.Data;
import org.cishell.framework.data.DataProperty;
import org.cishell.framework.userprefs.UserPrefsProperty;
import org.cishell.reference.gui.menumanager.Activator;
import org.cishell.reference.gui.menumanager.menu.metatypewrapper.ParamMetaTypeProvider;
import org.cishell.reference.service.metatype.BasicMetaTypeProvider;
import org.cishell.service.conversion.ConversionException;
import org.cishell.service.conversion.Converter;
import org.cishell.service.guibuilder.GUIBuilderService;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.log.LogService;
import org.osgi.service.metatype.AttributeDefinition;
import org.osgi.service.metatype.MetaTypeProvider;
import org.osgi.service.metatype.MetaTypeService;
import org.osgi.service.metatype.ObjectClassDefinition;
public class AlgorithmWrapper implements Algorithm, AlgorithmProperty, ProgressTrackable {
protected ServiceReference serviceReference;
protected BundleContext bundleContext;
protected CIShellContext ciShellContext;
protected Data[] originalData;
protected Data[] data;
protected Converter[][] converters;
protected ProgressMonitor progressMonitor;
protected Algorithm algorithm;
protected Dictionary<String, Object> parameters;
public AlgorithmWrapper(
ServiceReference serviceReference,
BundleContext bundleContext,
CIShellContext ciShellContext,
Data[] originalData,
Data[] data,
Converter[][] converters) {
this.serviceReference = serviceReference;
this.bundleContext = bundleContext;
this.ciShellContext = ciShellContext;
this.originalData = originalData;
this.data = data;
this.converters = converters;
this.progressMonitor = null;
}
public Dictionary<String, Object> getParameters() {
// Return a cloned Dictionary
return cloneDictionary(parameters);
}
private <K, V> Dictionary<K, V> cloneDictionary(Dictionary<K, V> source) {
Dictionary<K, V> cloned = new Hashtable<K, V>();
for (Enumeration<K> keys = source.keys(); keys.hasMoreElements();) {
K key = keys.nextElement();
cloned.put(key, source.get(key));
}
return cloned;
}
/**
* @see org.cishell.framework.algorithm.Algorithm#execute()
*/
public Data[] execute() throws AlgorithmExecutionException {
try {
AlgorithmFactory factory = getAlgorithmFactory(bundleContext, this.serviceReference);
if (factory == null) {
return null;
}
String pid = (String) this.serviceReference.getProperty(Constants.SERVICE_PID);
// Convert input data to the correct format.
boolean conversionSuccessful = tryConvertingDataToRequiredFormat(data, converters);
if (!conversionSuccessful) {
return null;
}
boolean inputIsValid = testDataValidityIfPossible(factory, data);
if (!inputIsValid) {
return null;
}
// Create algorithm parameters.
String metatypePID = getMetaTypeID(this.serviceReference);
// TODO: Refactor this.
MetaTypeProvider provider = null;
try {
provider = getPossiblyMutatedMetaTypeProvider(metatypePID, pid, factory);
} catch (AlgorithmCreationFailedException e) {
String format =
"An error occurred when creating the algorithm \"%s\" with the data you " +
"provided. (Reason: %s)";
String logMessage = String.format(
format,
this.serviceReference.getProperty(AlgorithmProperty.LABEL),
e.getMessage());
log(LogService.LOG_ERROR, logMessage, e);
return null;
} catch (Exception e) {
log(LogService.LOG_ERROR, e.getMessage(), e);
return null;
}
parameters = getUserEnteredParameters(metatypePID, provider);
// Check to see if the user canceled the operation.
if (parameters == null) {
return null;
}
printParameters(metatypePID, provider, parameters);
// Create the algorithm.
algorithm = createAlgorithm(
factory,
data,
parameters,
new CIShellContextDelegate(this.serviceReference, this.ciShellContext));
if (algorithm == null) {
return null;
}
trackAlgorithmIfPossible(algorithm);
// Execute the algorithm.
Data[] outData = tryExecutingAlgorithm(algorithm);
if (outData == null) {
return null;
}
// TODO: Refactor this into a method?
for (Data datum : outData) {
datum.getMetadata().put(DataProperty.SERVICE_REFERENCE, this.serviceReference);
}
// Process and return the algorithm's output.
doParentage(outData);
outData = removeNullData(outData);
addDataToDataManager(outData);
return outData;
} catch (Exception e) {
GUIBuilderService builder = (GUIBuilderService) this.ciShellContext
.getService(GUIBuilderService.class.getName());
String errorMessage = "An error occurred while preparing to run "
+ "the algorithm \"" + this.serviceReference.getProperty(AlgorithmProperty.LABEL)
+ ".\"";
builder.showError("Error!", errorMessage, e);
log(LogService.LOG_ERROR, errorMessage, e);
}
return null;
}
protected AlgorithmFactory getAlgorithmFactory(
BundleContext bundleContext, ServiceReference serviceReference) {
AlgorithmFactory algorithmFactory =
(AlgorithmFactory) bundleContext.getService(serviceReference);
if (algorithmFactory == null) {
String errorMessage = "Could not create AlgorithmFactory for the algorithm "
+ "\""
+ serviceReference.getProperty(AlgorithmProperty.LABEL)
+ "\".";
String details = "The algorithm's pid was \""
+ serviceReference.getProperty(Constants.SERVICE_PID)
+ "\" (potentially useful for debugging purposes).";
GUIBuilderService builder = (GUIBuilderService) this.ciShellContext
.getService(GUIBuilderService.class.getName());
builder.showError("Error!", errorMessage, details);
this.log(LogService.LOG_ERROR, errorMessage);
}
return algorithmFactory;
}
protected Algorithm createAlgorithm(AlgorithmFactory factory, Data[] data,
Dictionary<String, Object> parameters, CIShellContext ciContext) {
final String algorithmName = (String) this.serviceReference
.getProperty(AlgorithmProperty.LABEL);
// TODO: Call on algorithm invocation service here.
try {
return factory.createAlgorithm(data, parameters, ciContext);
} catch (AlgorithmCreationCanceledException e) {
String logMessage = String.format(
"The algorithm \"%s\" was canceled by the user.",
algorithmName, e.getMessage());
log(LogService.LOG_WARNING, logMessage, e);
return null;
} catch (AlgorithmCreationFailedException e) {
String format = "An error occurred when creating algorithm \"%s\". (Reason: %s)";
String errorMessage = String.format(format, algorithmName,
e.getMessage());
GUIBuilderService builder = (GUIBuilderService) ciContext
.getService(GUIBuilderService.class.getName());
builder.showError("Error!", errorMessage, e);
log(LogService.LOG_ERROR, errorMessage, e);
return null;
} catch (Exception e) {
String errorMessage = String
.format("Unexpected error occurred while creating algorithm \"%s\".",
algorithmName);
GUIBuilderService builder = (GUIBuilderService) ciContext
.getService(GUIBuilderService.class.getName());
// TODO: This is where uncaught exceptions are displayed.
builder.showError("Error!", errorMessage, e);
log(LogService.LOG_ERROR, errorMessage, e);
return null;
}
}
protected Data[] removeNullData(Data[] outData) {
if (outData != null) {
List<Data> goodData = new ArrayList<Data>();
for (Data data : outData) {
if (data != null) {
goodData.add(data);
}
}
outData = (Data[]) goodData.toArray(new Data[0]);
}
return outData;
}
protected void addDataToDataManager(Data[] outData) {
if (outData != null) {
DataManagerService dataManager = (DataManagerService) bundleContext
.getService(bundleContext
.getServiceReference(DataManagerService.class
.getName()));
if (outData.length != 0) {
for (int ii = 0; ii < outData.length; ii++) {
dataManager.addData(outData[ii]);
}
Data[] dataToSelect = new Data[] { outData[0] };
dataManager.setSelectedData(dataToSelect);
}
}
}
protected Data[] tryExecutingAlgorithm(Algorithm algorithm) {
Data[] outData = null;
final String algorithmName = (String) this.serviceReference
.getProperty(AlgorithmProperty.LABEL);
try {
outData = algorithm.execute();
} catch (AlgorithmCanceledException e) {
String logMessage = String.format(
"The algorithm: \"%s\" was canceled by the user.",
algorithmName, e.getMessage());
log(LogService.LOG_WARNING, logMessage, e);
} catch (AlgorithmExecutionException e) {
String logMessage = String.format(
"The algorithm: \"%s\" had an error while executing: %s",
algorithmName, e.getMessage());
log(LogService.LOG_ERROR, logMessage, e);
} catch (RuntimeException e) {
GUIBuilderService builder = (GUIBuilderService) this.ciShellContext
.getService(GUIBuilderService.class.getName());
String errorMessage = String
.format("An unxpected error occurred while executing the algorithm \"%s\".",
algorithmName);
builder.showError("Error!", errorMessage, e);
}
return outData;
}
protected boolean tryConvertingDataToRequiredFormat(Data[] data,
Converter[][] converters) {
for (int i = 0; i < data.length; i++) {
if (converters[i] != null) {
try {
data[i] = converters[i][0].convert(data[i]);
} catch (ConversionException e) {
String logMessage = String
.format("Error: Unable to convert data for use by the algorithm:%n %s",
e.getMessage());
log(LogService.LOG_ERROR, logMessage, e);
e.printStackTrace();
return false;
}
if (data[i] == null && i < (data.length - 1)) {
log(LogService.LOG_ERROR, "The converter: "
+ converters[i].getClass().getName()
+ " returned a null result where data was "
+ "expected when converting the data to give "
+ "the algorithm.");
return false;
}
converters[i] = null;
}
}
return true;
}
protected boolean testDataValidityIfPossible(AlgorithmFactory factory,
Data[] data) {
if (factory instanceof DataValidator) {
String validation = ((DataValidator) factory).validate(data);
if (validation != null && validation.length() > 0) {
String label = (String) this.serviceReference
.getProperty(LABEL);
if (label == null) {
label = "Algorithm";
}
log(LogService.LOG_ERROR, "INVALID DATA: The data given to \""
+ label + "\" is incompatible for this reason: "
+ validation);
return false;
}
}
return true;
}
protected String getMetaTypeID(ServiceReference ref) {
String pid = (String) ref.getProperty(Constants.SERVICE_PID);
String metatype_pid = (String) ref.getProperty(PARAMETERS_PID);
if (metatype_pid == null) {
metatype_pid = pid;
}
return metatype_pid;
}
protected MetaTypeProvider getPossiblyMutatedMetaTypeProvider(
String metatypePID, String pid, AlgorithmFactory factory)
throws AlgorithmCreationFailedException {
MetaTypeProvider provider = null;
MetaTypeService metaTypeService = (MetaTypeService) Activator
.getService(MetaTypeService.class.getName());
if (metaTypeService != null) {
provider = metaTypeService
.getMetaTypeInformation(this.serviceReference.getBundle());
}
if ((factory instanceof ParameterMutator) && (provider != null)) {
try {
ObjectClassDefinition objectClassDefinition = provider
.getObjectClassDefinition(metatypePID, null);
if (objectClassDefinition == null) {
logNullOCDWarning(pid, metatypePID);
}
try {
objectClassDefinition = ((ParameterMutator) factory)
.mutateParameters(data, objectClassDefinition);
if (objectClassDefinition != null) {
provider = new BasicMetaTypeProvider(
objectClassDefinition);
}
} catch (AllParametersMutatedOutException e) {
provider = null;
}
} catch (IllegalArgumentException e) {
log(LogService.LOG_DEBUG, pid + " has an invalid metatype id: "
+ metatypePID, e);
}
}
if (provider != null) {
provider = wrapProvider(this.serviceReference, provider);
}
return provider;
}
protected void trackAlgorithmIfPossible(Algorithm algorithm) {
if (progressMonitor != null && algorithm instanceof ProgressTrackable) {
((ProgressTrackable) algorithm).setProgressMonitor(progressMonitor);
}
}
protected Dictionary<String, Object> getUserEnteredParameters(
String metatypePID, MetaTypeProvider provider) {
Dictionary<String, Object> parameters = new Hashtable<String, Object>();
if (provider != null) {
GUIBuilderService builder = (GUIBuilderService) this.ciShellContext
.getService(GUIBuilderService.class.getName());
// TODO: Make builder.createGUIAndWait return a Dictionary<String,
// Object>.
parameters = builder.createGUIandWait(metatypePID, provider);
}
return parameters;
}
/*
* Wrap the provider to provide special functionality, such as overriding
* default values of attributes through preferences.
*/
protected MetaTypeProvider wrapProvider(ServiceReference algRef,
MetaTypeProvider unwrappedProvider) {
ConfigurationAdmin ca = getConfigurationAdmin();
if (ca != null && hasParamDefaultPreferences(algRef)) {
String standardServicePID = (String) algRef
.getProperty(Constants.SERVICE_PID);
String paramOverrideConfPID = standardServicePID
+ UserPrefsProperty.PARAM_PREFS_CONF_SUFFIX;
try {
Configuration defaultParamValueOverrider = ca.getConfiguration(
paramOverrideConfPID, null);
Dictionary defaultParamOverriderDict = defaultParamValueOverrider
.getProperties();
MetaTypeProvider wrappedProvider = new ParamMetaTypeProvider(
unwrappedProvider, defaultParamOverriderDict);
return wrappedProvider;
} catch (IOException e) {
return unwrappedProvider;
}
} else {
}
return unwrappedProvider;
}
protected boolean hasParamDefaultPreferences(ServiceReference algRef) {
String prefsToPublish = (String) algRef
.getProperty(UserPrefsProperty.PREFS_PUBLISHED_KEY);
if (prefsToPublish == null) {
return false;
}
return prefsToPublish
.contains(UserPrefsProperty.PUBLISH_PARAM_DEFAULT_PREFS_VALUE);
}
protected void log(int logLevel, String message) {
LogService logger = (LogService) this.ciShellContext
.getService(LogService.class.getName());
if (logger != null) {
logger.log(this.serviceReference, logLevel, message);
} else {
System.out.println(message);
}
}
protected void log(int logLevel, String message, Throwable exception) {
LogService logger = (LogService) this.ciShellContext
.getService(LogService.class.getName());
if (logger != null) {
logger.log(this.serviceReference, logLevel, message, exception);
} else {
System.out.println(message);
exception.printStackTrace();
}
}
protected void printParameters(String metatype_pid,
MetaTypeProvider provider, Dictionary parameters) {
LogService logger = getLogService();
Map idToLabelMap = setupIdToLabelMap(metatype_pid, provider);
if (logger != null && !parameters.isEmpty()) {
// adjust to log all input parameters in one block
StringBuffer inputParams = new StringBuffer("\n"
+ "Input Parameters:");
for (Enumeration e = parameters.keys(); e.hasMoreElements();) {
String key = (String) e.nextElement();
Object value = parameters.get(key);
key = (String) idToLabelMap.get(key);
inputParams.append("\n" + key + ": " + value);
}
logger.log(this.serviceReference, LogService.LOG_INFO,
inputParams.toString());
}
}
protected Map setupIdToLabelMap(String metatype_pid,
MetaTypeProvider provider) {
Map idToLabelMap = new HashMap();
if (provider != null) {
ObjectClassDefinition ocd = null;
try {
ocd = provider.getObjectClassDefinition(metatype_pid, null);
if (ocd != null) {
AttributeDefinition[] attr = ocd
.getAttributeDefinitions(ObjectClassDefinition.ALL);
for (int i = 0; i < attr.length; i++) {
String id = attr[i].getID();
String label = attr[i].getName();
idToLabelMap.put(id, label);
}
}
} catch (IllegalArgumentException e) {
}
}
return idToLabelMap;
}
// only does anything if parentage=default so far...
protected void doParentage(Data[] outData) {
// make sure the parent set is the original Data and not the
// converted data...
if (outData != null && data != null && originalData != null
&& originalData.length == data.length) {
for (int i = 0; i < outData.length; i++) {
if (outData[i] != null) {
Object parent = outData[i].getMetadata().get(
DataProperty.PARENT);
if (parent != null) {
for (int j = 0; j < data.length; j++) {
if (parent == data[j]) {
outData[i].getMetadata().put(
DataProperty.PARENT, originalData[j]);
break;
}
}
}
}
}
}
// Check and act on parentage settings
String parentage = (String) this.serviceReference
.getProperty("parentage");
if (parentage != null) {
parentage = parentage.trim();
if (parentage.equalsIgnoreCase("default")) {
if (originalData != null && originalData.length > 0
&& originalData[0] != null) {
for (int i = 0; i < outData.length; i++) {
// If they don't have a parent set already then we set one
if (outData[i] != null
&& outData[i].getMetadata().get(
DataProperty.PARENT) == null) {
outData[i].getMetadata().put(DataProperty.PARENT,
originalData[0]);
}
}
}
}
}
}
private LogService getLogService() {
ServiceReference serviceReference = bundleContext
.getServiceReference(DataManagerService.class.getName());
LogService log = null;
if (serviceReference != null) {
log = (LogService) bundleContext.getService(bundleContext
.getServiceReference(LogService.class.getName()));
}
return log;
}
private ConfigurationAdmin getConfigurationAdmin() {
ServiceReference serviceReference = bundleContext
.getServiceReference(ConfigurationAdmin.class.getName());
ConfigurationAdmin ca = null;
if (serviceReference != null) {
ca = (ConfigurationAdmin) bundleContext.getService(bundleContext
.getServiceReference(ConfigurationAdmin.class.getName()));
}
return ca;
}
private void logNullOCDWarning(String pid, String metatype_pid) {
this.log(LogService.LOG_WARNING,
"Warning: could not get object class definition '"
+ metatype_pid + "' from the algorithm '" + pid + "'");
}
public ProgressMonitor getProgressMonitor() {
if (algorithm instanceof ProgressTrackable) {
return progressMonitor;
} else {
return null;
}
}
public void setProgressMonitor(ProgressMonitor monitor) {
progressMonitor = monitor;
}
}