/* * 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; } }