/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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.
*
* Copyright 2005 - 2009 Pentaho Corporation. All rights reserved.
*
*
* @created Jun 28, 2005
* @author James Dixon
*/
package org.pentaho.platform.plugin.action.javascript;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.pentaho.actionsequence.dom.ActionInputConstant;
import org.pentaho.actionsequence.dom.IActionOutput;
import org.pentaho.actionsequence.dom.actions.JavascriptAction;
import org.pentaho.commons.connection.IPentahoResultSet;
import org.pentaho.platform.api.engine.IActionSequenceResource;
import org.pentaho.platform.engine.services.solution.ComponentBase;
import org.pentaho.platform.plugin.action.messages.Messages;
import org.pentaho.platform.plugin.condition.javascript.RhinoScriptable;
import org.pentaho.platform.plugin.services.connections.javascript.JavaScriptResultSet;
/**
* @author James Dixon
*
* TODO To change the template for this generated type comment go to Window -
* Preferences - Java - Code Style - Code Templates
*/
public class JavascriptRule extends ComponentBase {
/**
*
*/
private static final long serialVersionUID = -8305132222755452461L;
private boolean oldStyleOutputs;
@Override
public Log getLogger() {
return LogFactory.getLog(JavascriptRule.class);
}
@Override
protected boolean validateSystemSettings() {
// This component does not have any system settings to validate
return true;
}
@Override
protected boolean validateAction() {
boolean actionValidated = true;
JavascriptAction jscriptAction = null;
if (getActionDefinition() instanceof JavascriptAction) {
jscriptAction = (JavascriptAction) getActionDefinition();
// get report connection setting
if (jscriptAction.getScript() == ActionInputConstant.NULL_INPUT) {
error(Messages.getInstance().getErrorString("JSRULE.ERROR_0001_SCRIPT_NOT_DEFINED", getActionName())); //$NON-NLS-1$
actionValidated = false;
}
if (actionValidated) {
if (jscriptAction.getOutputs().length <= 0) {
error(Messages.getInstance().getString("Template.ERROR_0002_OUTPUT_COUNT_WRONG")); //$NON-NLS-1$
actionValidated = false;
}
}
if (actionValidated) {
IActionOutput[] outputs = jscriptAction.getOutputs(); //getOutputNames();
/*
* if the number of action def outputs is more than 1 and
* if the fist output var defined in the input section is named output1
* then the xaction is defined oldstyle and we should check if there is
* corresponding o/p variable defined in the input section for each output
* var defined in the output section.
* NOTE: With the old style you can only have output defined as "output#" where # is a number.
*/
if ((outputs.length > 1) && (jscriptAction.getInput("output1") != ActionInputConstant.NULL_INPUT)) { //$NON-NLS-1$
oldStyleOutputs = true;
for (int i = 1; i <= outputs.length; ++i) {
if (jscriptAction.getInput("output" + i) == ActionInputConstant.NULL_INPUT) { //$NON-NLS-1$
error(Messages.getInstance().getErrorString(
"JavascriptRule.ERROR_0006_NO_MAPPED_OUTPUTS", String.valueOf(outputs.length), String.valueOf(i))); //$NON-NLS-1$
actionValidated = false;
break;
}
}
}
}
} else {
actionValidated = false;
error(Messages.getInstance().getErrorString(
"ComponentBase.ERROR_0001_UNKNOWN_ACTION_TYPE", getActionDefinition().getElement().asXML())); //$NON-NLS-1$
}
return actionValidated;
}
@Override
public void done() {
}
/*
* (non-Javadoc)
*
* @see org.pentaho.component.ComponentBase#execute()
*/
@Override
protected boolean executeAction() {
Context cx = ContextFactory.getGlobal().enterContext();
StringBuffer buffer = new StringBuffer();
@SuppressWarnings("unchecked")
Iterator<String> iter = getResourceNames().iterator();
while (iter.hasNext()) {
IActionSequenceResource resource = getResource(iter.next().toString());
// If this is a javascript resource then append it to the script string
if ("text/javascript".equalsIgnoreCase(resource.getMimeType())) { //$NON-NLS-1$
buffer.append(getResourceAsString(resource));
}
}
List<String> outputNames = new ArrayList<String>();
JavascriptAction jscriptAction = (JavascriptAction) getActionDefinition();
IActionOutput[] actionOutputs = jscriptAction.getOutputs();
if (actionOutputs.length == 1) {
String outputName = actionOutputs[0].getName();
outputNames.add(outputName);
} else {
if (oldStyleOutputs) {
int i = 1;
while (true) {
if (jscriptAction.getInput("output" + i) != ActionInputConstant.NULL_INPUT) { //$NON-NLS-1$
outputNames.add(jscriptAction.getInput("output" + i).getStringValue()); //$NON-NLS-1$
} else {
break;
}
i++;
}
} else {
for (IActionOutput element : actionOutputs) {
outputNames.add(element.getName());
}
}
}
boolean success = false;
try {
String script = jscriptAction.getScript().getStringValue();
if (script == null) {
error(Messages.getInstance().getErrorString("JSRULE.ERROR_0001_SCRIPT_NOT_DEFINED", getActionName())); //$NON-NLS-1$
} else {
buffer.append(script);
script = buffer.toString();
if (ComponentBase.debug) {
debug("script=" + script); //$NON-NLS-1$
}
try {
ScriptableObject scriptable = new RhinoScriptable();
// initialize the standard javascript objects
Scriptable scope = cx.initStandardObjects(scriptable);
Object resultObject = executeScript(scriptable, scope, script, cx);
if (oldStyleOutputs) {
if (resultObject instanceof org.mozilla.javascript.NativeArray) {
// we need to convert this to an ArrayList
NativeArray jsArray = (NativeArray) resultObject;
int length = (int) jsArray.getLength();
for (int i = 0; i < length; i++) {
Object value = jsArray.get(i, scriptable);
if (i < outputNames.size()) {
jscriptAction.getOutput(outputNames.get(i).toString()).setValue(convertWrappedJavaObject(value));
} else {
break;
}
}
} else {
jscriptAction.getOutput(outputNames.get(0).toString()).setValue(convertWrappedJavaObject(resultObject));
}
} else {
if ((outputNames.size() == 1) && (resultObject != null)) {
jscriptAction.getOutput(outputNames.get(0).toString()).setValue(convertWrappedJavaObject(resultObject));
} else {
List<String> setOutputs = new ArrayList<String>(outputNames.size());
Object[] ids = ScriptableObject.getPropertyIds(scope);
for (Object element : ids) {
int idx = outputNames.indexOf(element.toString());
if (idx >= 0) {
jscriptAction.getOutput(outputNames.get(idx).toString()).setValue(
convertWrappedJavaObject(ScriptableObject.getProperty(scope, (String) element)));
setOutputs.add(outputNames.get(idx));
}
}
// Javascript Component defined an output, but
// didn't set it to anything.
// So, set it to null.
if (setOutputs.size() != outputNames.size()) {
for (int i = 0; i < outputNames.size(); i++) {
if (setOutputs.indexOf(outputNames.get(i)) < 0) {
// An output that wasn't set in the
// javascript component
jscriptAction.getOutput(outputNames.get(i).toString()).setValue(null);
}
}
}
}
}
success = true;
} catch (Exception e) {
error(Messages.getInstance().getErrorString("JSRULE.ERROR_0003_EXECUTION_FAILED"), e); //$NON-NLS-1$
}
}
} finally {
Context.exit();
}
return success;
}
protected Object executeScript(final ScriptableObject scriptable, final Scriptable scope, final String script,
final Context cx) throws Exception {
ScriptableObject.defineClass(scope, JavaScriptResultSet.class);
@SuppressWarnings("unchecked")
Set<String> inputNames = getInputNames();
Iterator<String> inputNamesIterator = inputNames.iterator();
String inputName;
Object inputValue;
while (inputNamesIterator.hasNext()) {
inputName = (String) inputNamesIterator.next();
if (inputName.indexOf('-') >= 0) {
throw new IllegalArgumentException(Messages.getInstance().getErrorString("JSRULE.ERROR_0006_INVALID_JS_VARIABLE", inputName)); //$NON-NLS-1$
}
inputValue = getInputValue(inputName);
Object wrapper;
if (inputValue instanceof IPentahoResultSet) {
JavaScriptResultSet results = new JavaScriptResultSet();
//Required as of Rhino 1.7R1 to resolve caching, base object
//inheritance and property tree
results.setPrototype(scriptable);
results.setResultSet((IPentahoResultSet) inputValue);
wrapper = Context.javaToJS(inputValue, results);
} else {
wrapper = Context.javaToJS(inputValue, scope);
}
ScriptableObject.putProperty(scope, inputName, wrapper);
}
// Add system out and this object to the scope
Object wrappedOut = Context.javaToJS(System.out, scope);
Object wrappedThis = Context.javaToJS(this, scope);
ScriptableObject.putProperty(scope, "out", wrappedOut); //$NON-NLS-1$
ScriptableObject.putProperty(scope, "rule", wrappedThis); //$NON-NLS-1$
// evaluate the script
return cx.evaluateString(scope, script, "<cmd>", 1, null); //$NON-NLS-1$
}
protected Object convertWrappedJavaObject(final Object obj) {
// If we wrap an object going in, and simply return the object,
// without unwrapping it, we're left with an object we can't
// use. Case in point was a Java array going in, and being
// wrapped as a org.mozilla.javascript.NativeJavaArray. On
// the way back into the context though, it stayed a mozilla
// object. This unwraps objects properly so that they can be
// recognized throughout the system.
if (obj instanceof org.mozilla.javascript.NativeJavaObject) {
return ((org.mozilla.javascript.NativeJavaObject) obj).unwrap();
} else {
return obj;
}
}
@Override
public boolean init() {
return true;
}
}