/*
* RapidMiner
*
* Copyright (C) 2001-2011 by Rapid-I and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapid-i.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.operator.meta;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.rapidminer.BreakpointListener;
import com.rapidminer.Process;
import com.rapidminer.RepositoryProcessLocation;
import com.rapidminer.operator.IOContainer;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.ProcessRootOperator;
import com.rapidminer.operator.ProcessSetupError;
import com.rapidminer.operator.ProcessSetupError.Severity;
import com.rapidminer.operator.SimpleProcessSetupError;
import com.rapidminer.operator.UserError;
import com.rapidminer.operator.ports.InputPortExtender;
import com.rapidminer.operator.ports.OutputPortExtender;
import com.rapidminer.operator.ports.metadata.MDTransformationRule;
import com.rapidminer.operator.ports.metadata.MetaData;
import com.rapidminer.operator.ports.metadata.SimpleMetaDataError;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeBoolean;
import com.rapidminer.parameter.ParameterTypeList;
import com.rapidminer.parameter.ParameterTypeRepositoryLocation;
import com.rapidminer.parameter.ParameterTypeString;
import com.rapidminer.parameter.UndefinedParameterError;
import com.rapidminer.repository.Entry;
import com.rapidminer.repository.ProcessEntry;
import com.rapidminer.repository.RepositoryException;
import com.rapidminer.repository.RepositoryLocation;
import com.rapidminer.tools.LogService;
import com.rapidminer.tools.Observable;
import com.rapidminer.tools.Observer;
import com.rapidminer.tools.XMLException;
/** This operator can be used to embed a complete process definition into the current
* process definition.
* The process must have been written into a file before and will be loaded and
* executed when the current process reaches this operator. Optionally, the input
* of this operator can be used as input for the embedded process. In both cases,
* the output of the process will be delivered as output of this operator. Please note
* that validation checks will not work for process containing an operator of this
* type since the check cannot be performed without actually loading the process.
*
* @author Ingo Mierswa
*/
public class ProcessEmbeddingOperator extends Operator {
private final InputPortExtender inputExtender = new InputPortExtender("input", getInputPorts());
private final OutputPortExtender outputExtender = new OutputPortExtender("result", getOutputPorts());
/** The parameter name for "The process file which should be encapsulated by this operator" */
public static final String PARAMETER_PROCESS_FILE = "process_location";
/** The parameter name for "Indicates if the operator input should be used as input of the process" */
public static final String PARAMETER_USE_INPUT = "use_input";
/** The parameter name for "Indicates if the operator output should be stored to a repository if the
* context of the embedded process defines output locations" */
public static final String PARAMETER_STORE_OUTPUT = "store_output";
/** Determines whether meta data is propagated through the included process. */
public static final String PARAMETER_PROPAGATE_METADATA_RECURSIVELY = "propagate_metadata_recursively";
/** If true, {@link #cachedProcess} will be used in {@link #loadIncludedProcess()}. */
public static final String PARAMETER_CACHE_PROCESS = "cache_process";
public static final String PARAMETER_MACROS = "macros";
public static final String PARAMETER_MACRO_NAME = "macro_name";
public static final String PARAMETER_MACRO_VALUE = "macro_value";
private Process cachedProcess;
private ProcessSetupError cachedError = null;
public ProcessEmbeddingOperator(OperatorDescription description) {
super(description);
inputExtender.start();
outputExtender.start();
getParameters().addObserver(new Observer<String>() {
@Override
public void update(Observable<String> observable, String arg) {
cachedProcess = null;
cachedError = null;
}
}, false);
getTransformer().addRule(new MDTransformationRule() {
@Override
public void transformMD() {
if (getParameterAsBoolean(PARAMETER_PROPAGATE_METADATA_RECURSIVELY)) {
if (cachedProcess != null) {
ProcessRootOperator root = cachedProcess.getRootOperator();
int requires = root.getSubprocess(0).getInnerSources().getNumberOfConnectedPorts();
int gets = getInputPorts().getNumberOfConnectedPorts();
if (requires != gets) {
getInputPorts().getPortByIndex(0).addError(new SimpleMetaDataError(Severity.ERROR, getInputPorts().getPortByIndex(0), "included_process_input_mismatch", requires, gets));
}
int delivers = root.getSubprocess(0).getInnerSinks().getNumberOfConnectedPorts();
int consumes = getOutputPorts().getNumberOfConnectedPorts();
if (delivers != consumes) {
getInputPorts().getPortByIndex(0).addError(new SimpleMetaDataError(Severity.WARNING, getInputPorts().getPortByIndex(0), "included_process_output_mismatch", delivers, consumes));
}
if (getParameterAsBoolean(PARAMETER_USE_INPUT)) {
root.deliverInputMD(inputExtender.getMetaData(false));
}
root.transformMetaData();
List<MetaData> result = root.getResultMetaData();
outputExtender.deliverMetaData(result);
}
}
}
});
}
@Override
protected void performAdditionalChecks() {
super.performAdditionalChecks();
if (getParameterAsBoolean(PARAMETER_PROPAGATE_METADATA_RECURSIVELY)) {
if (cachedProcess == null) {
try {
cachedProcess = loadIncludedProcess();
} catch (Exception e) {
cachedError = new SimpleProcessSetupError(Severity.ERROR, getPortOwner(), "cannot_load_included_process", e.getMessage());
addError(cachedError);
}
} else {
if (cachedError != null) {
addError(cachedError);
}
}
}
}
@Override
public void doWork() throws OperatorException {
Process process;
try {
process = loadIncludedProcess();
} catch (RepositoryException e) {
throw new UserError(this, e, 312, getParameterAsString(PARAMETER_PROCESS_FILE), e.getMessage());
}
// define macros
Map<String, String> macroMap = new HashMap<String, String>();
List<String[]> macros = getParameterList(PARAMETER_MACROS);
if (macros != null) {
for (String[] macroPair : macros) {
String macroName = macroPair[0];
String macroValue = macroPair[1];
macroMap.put(macroName, macroValue);
}
}
// run process
IOContainer result = null;
if (getParameterAsBoolean(PARAMETER_USE_INPUT)) {
result = process.run(new IOContainer(inputExtender.getData(false)),LogService.UNKNOWN_LEVEL, macroMap, getParameterAsBoolean(PARAMETER_STORE_OUTPUT));
} else {
result = process.run(new IOContainer(),LogService.UNKNOWN_LEVEL, macroMap, getParameterAsBoolean(PARAMETER_STORE_OUTPUT));
}
outputExtender.deliver(Arrays.asList(result.getIOObjects()));
}
private Process loadIncludedProcess() throws UndefinedParameterError, UserError, RepositoryException {
boolean useCache = getParameterAsBoolean(PARAMETER_CACHE_PROCESS);
if (useCache && cachedProcess != null) {
return cachedProcess;
}
RepositoryLocation location = getParameterAsRepositoryLocation(PARAMETER_PROCESS_FILE);
Entry entry = location.locateEntry();
if (entry == null) {
throw new RepositoryException("Entry '"+location+"' does not exist.");
} else if (entry instanceof ProcessEntry) {
Process process;
try {
process = new RepositoryProcessLocation(location).load(null);
process.setRepositoryAccessor(getProcess().getRepositoryAccessor());
for (Operator op : process.getRootOperator().getAllInnerOperators()) {
op.setBreakpoint(BreakpointListener.BREAKPOINT_AFTER, false);
op.setBreakpoint(BreakpointListener.BREAKPOINT_BEFORE, false);
}
} catch (IOException e) {
throw new UserError(this, 302, location, e.getMessage());
} catch (XMLException e) {
throw new UserError(this, 401, e.getMessage());
}
if (useCache) {
cachedProcess = process;
}
return process;
} else {
throw new RepositoryException("Entry '"+location+"' is not a data entry, but "+entry.getType());
}
//
// String relativeProcessLocation = getParameterAsString(PARAMETER_PROCESS_FILE);
// RepositoryLocation resolvedLocation;
// if ((getProcess() != null) && (getProcess().getRepositoryLocation() != null)) {
// try {
// resolvedLocation = new RepositoryLocation(getProcess().getRepositoryLocation().parent(), relativeProcessLocation);
// } catch (MalformedRepositoryLocationException e) {
// throw e.makeUserError(this);
// }
// } else {
// getLogger().info("Process is not contained in a repository. Trying to resolve absolute location.");
// try {
// resolvedLocation = new RepositoryLocation(relativeProcessLocation);
// } catch (MalformedRepositoryLocationException e) {
// throw e.makeUserError(this);
// }
// }
}
@Override
public List<ParameterType> getParameterTypes() {
List<ParameterType> types = super.getParameterTypes();
types.add(new ParameterTypeRepositoryLocation(PARAMETER_PROCESS_FILE, "The process location which should be encapsulated by this operator", false));
types.add(new ParameterTypeBoolean(PARAMETER_USE_INPUT, "Indicates if the operator input should be used as input of the process", false));
types.add(new ParameterTypeBoolean(PARAMETER_STORE_OUTPUT, "Indicates if the operator output should be stored (if the context of the embedded process defines output locations).", true));
types.add(new ParameterTypeBoolean(PARAMETER_PROPAGATE_METADATA_RECURSIVELY, "Determines whether meta data is propagated through the included process.", false));
types.add(new ParameterTypeBoolean(PARAMETER_CACHE_PROCESS, "If checked, the process will not be loaded during execution.", false));
types.add(new ParameterTypeList(PARAMETER_MACROS, "Defines macros for this sub-process.", new ParameterTypeString(PARAMETER_MACRO_NAME, "The name of the macro.", false), new ParameterTypeString(PARAMETER_MACRO_VALUE, "The value of the macro.", false), true));
return types;
}
}