001: /*
002: * Copyright 2006 Pentaho Corporation. All rights reserved.
003: * This software was developed by Pentaho Corporation and is provided under the terms
004: * of the Mozilla Public License, Version 1.1, or any later version. You may not use
005: * this file except in compliance with the license. If you need a copy of the license,
006: * please go to http://www.mozilla.org/MPL/MPL-1.1.txt. The Original Code is the Pentaho
007: * BI Platform. The Initial Developer is Pentaho Corporation.
008: *
009: * Software distributed under the Mozilla Public License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. Please refer to
011: * the license for the specific language governing your rights and limitations.
012: *
013: * @created Jun 28, 2005
014: * @author James Dixon
015: */
016:
017: package org.pentaho.plugin.javascript;
018:
019: import java.util.ArrayList;
020: import java.util.Iterator;
021: import java.util.Set;
022:
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025: import org.mozilla.javascript.Context;
026: import org.mozilla.javascript.NativeArray;
027: import org.mozilla.javascript.Scriptable;
028: import org.mozilla.javascript.ScriptableObject;
029: import org.pentaho.actionsequence.dom.ActionOutput;
030: import org.pentaho.actionsequence.dom.IActionInputValueProvider;
031: import org.pentaho.actionsequence.dom.actions.JavascriptAction;
032: import org.pentaho.commons.connection.IPentahoResultSet;
033: import org.pentaho.data.connection.javascript.JavaScriptResultSet;
034: import org.pentaho.data.connection.javascript.RhinoScriptable;
035: import org.pentaho.core.solution.IActionResource;
036: import org.pentaho.messages.Messages;
037: import org.pentaho.plugin.ComponentBase;
038:
039: /**
040: * @author James Dixon
041: *
042: * TODO To change the template for this generated type comment go to Window -
043: * Preferences - Java - Code Style - Code Templates
044: */
045: public class JavascriptRule extends ComponentBase {
046:
047: /**
048: *
049: */
050: private static final long serialVersionUID = -8305132222755452461L;
051:
052: private boolean oldStyleOutputs;
053:
054: public Log getLogger() {
055: return LogFactory.getLog(JavascriptRule.class);
056: }
057:
058: protected boolean validateSystemSettings() {
059: // This component does not have any system settings to validate
060: return true;
061: }
062:
063: protected boolean validateAction() {
064: boolean actionValidated = true;
065: JavascriptAction jscriptAction = null;
066:
067: if (getActionDefinition() instanceof JavascriptAction) {
068: jscriptAction = (JavascriptAction) getActionDefinition();
069:
070: // get report connection setting
071: if (jscriptAction.getScript() == IActionInputValueProvider.NULL_INPUT) {
072: error(Messages
073: .getErrorString(
074: "JSRULE.ERROR_0001_SCRIPT_NOT_DEFINED", getActionName())); //$NON-NLS-1$
075: actionValidated = false;
076: }
077:
078: if (actionValidated) {
079: if (jscriptAction.getAllOutputParams().length <= 0) {
080: error(Messages
081: .getString("Template.ERROR_0002_OUTPUT_COUNT_WRONG")); //$NON-NLS-1$
082: actionValidated = false;
083: }
084: }
085:
086: if (actionValidated) {
087: ActionOutput[] outputs = jscriptAction
088: .getAllOutputParams(); //getOutputNames();
089: /*
090: * if the number of action def outputs is more than 1 and
091: * if the fist output var defined in the input section is named output1
092: * then the xaction is defined oldstyle and we should check if there is
093: * corresponding o/p variable defined in the input section for each output
094: * var defined in the output section.
095: * NOTE: With the old style you can only have output defined as "output#" where # is a number.
096: */
097: if (outputs.length > 1
098: && jscriptAction.getActionInputValue("output1") != IActionInputValueProvider.NULL_INPUT) { //$NON-NLS-1$
099: oldStyleOutputs = true;
100: for (int i = 1; i <= outputs.length; ++i) {
101: if (jscriptAction
102: .getActionInputValue("output" + i) == IActionInputValueProvider.NULL_INPUT) { //$NON-NLS-1$
103: error(Messages
104: .getErrorString(
105: "JavascriptRule.ERROR_0006_NO_MAPPED_OUTPUTS", String.valueOf(outputs.length), String.valueOf(i))); //$NON-NLS-1$
106: actionValidated = false;
107: break;
108: }
109: }
110: }
111: }
112: } else {
113: actionValidated = false;
114: error(Messages
115: .getErrorString(
116: "ComponentBase.ERROR_0001_UNKNOWN_ACTION_TYPE", getActionDefinition().getElement().asXML())); //$NON-NLS-1$
117: }
118:
119: return actionValidated;
120: }
121:
122: public void done() {
123: }
124:
125: /*
126: * (non-Javadoc)
127: *
128: * @see org.pentaho.component.ComponentBase#execute()
129: */
130: protected boolean executeAction() {
131: Context cx = Context.enter();
132: StringBuffer buffer = new StringBuffer();
133: Iterator iter = getResourceNames().iterator();
134: while (iter.hasNext()) {
135: IActionResource resource = getResource(iter.next()
136: .toString());
137: // If this is a javascript resource then append it to the script string
138: if ("text/javascript".equalsIgnoreCase(resource.getMimeType())) { //$NON-NLS-1$
139: buffer.append(getResourceAsString(resource));
140: }
141: }
142:
143: ArrayList outputNames = new ArrayList();
144: JavascriptAction jscriptAction = (JavascriptAction) getActionDefinition();
145:
146: ActionOutput[] actionOutputs = jscriptAction
147: .getAllOutputParams();
148: if (actionOutputs.length == 1) {
149: String outputName = actionOutputs[0].getName();
150: outputNames.add(outputName);
151: } else {
152: if (oldStyleOutputs) {
153: int i = 1;
154: while (true) {
155: if (jscriptAction.getActionInputValue("output" + i) != IActionInputValueProvider.NULL_INPUT) { //$NON-NLS-1$
156: outputNames.add(jscriptAction
157: .getActionInputValue("output" + i)
158: .getStringValue());
159: } else {
160: break;
161: }
162: i++;
163: }
164: } else {
165: for (int i = 0; i < actionOutputs.length; i++) {
166: outputNames.add(actionOutputs[i].getName());
167: }
168: }
169: }
170:
171: boolean success = false;
172: try {
173: String script = jscriptAction.getScript().getStringValue();
174: if (script == null) {
175: error(Messages
176: .getErrorString(
177: "JSRULE.ERROR_0001_SCRIPT_NOT_DEFINED", getActionName())); //$NON-NLS-1$
178: } else {
179: buffer.append(script);
180: script = buffer.toString();
181: if (debug)
182: debug("script=" + script); //$NON-NLS-1$
183: try {
184: ScriptableObject scriptable = new RhinoScriptable();
185: // initialize the standard javascript objects
186: Scriptable scope = cx
187: .initStandardObjects(scriptable);
188:
189: Object resultObject = executeScript(scriptable,
190: scope, script, cx);
191: if (oldStyleOutputs) {
192: if (resultObject instanceof org.mozilla.javascript.NativeArray) {
193: // we need to convert this to an ArrayList
194: NativeArray jsArray = (NativeArray) resultObject;
195: int length = (int) jsArray.getLength();
196: for (int i = 0; i < length; i++) {
197: Object value = jsArray.get(i,
198: scriptable);
199: if (i < outputNames.size()) {
200: jscriptAction
201: .getOutputParam(
202: outputNames.get(i)
203: .toString())
204: .setValue(
205: convertWrappedJavaObject(value));
206: } else {
207: break;
208: }
209: }
210: } else {
211: jscriptAction
212: .getOutputParam(
213: outputNames.get(0)
214: .toString())
215: .setValue(
216: convertWrappedJavaObject(resultObject));
217: }
218: } else {
219: if ((outputNames.size() == 1)
220: && (resultObject != null)) {
221: jscriptAction
222: .getOutputParam(
223: outputNames.get(0)
224: .toString())
225: .setValue(
226: convertWrappedJavaObject(resultObject));
227: } else {
228: ArrayList setOutputs = new ArrayList(
229: outputNames.size());
230: Object[] ids = ScriptableObject
231: .getPropertyIds(scope);
232: for (int i = 0; i < ids.length; i++) {
233: int idx = outputNames.indexOf(ids[i]
234: .toString());
235: if (idx >= 0) {
236: jscriptAction
237: .getOutputParam(
238: outputNames
239: .get(idx)
240: .toString())
241: .setValue(
242: convertWrappedJavaObject(ScriptableObject
243: .getProperty(
244: scope,
245: (String) ids[i])));
246: setOutputs
247: .add(outputNames.get(idx));
248: }
249: }
250: // Javascript Component defined an output, but
251: // didn't set it to anything.
252: // So, set it to null.
253: if (setOutputs.size() != outputNames.size()) {
254: for (int i = 0; i < outputNames.size(); i++) {
255: if (setOutputs.indexOf(outputNames
256: .get(i)) < 0) {
257: // An output that wasn't set in the
258: // javascript component
259: jscriptAction.getOutputParam(
260: outputNames.get(i)
261: .toString())
262: .setValue(null);
263: }
264: }
265: }
266: }
267:
268: }
269:
270: success = true;
271: } catch (Exception e) {
272: error(
273: Messages
274: .getErrorString("JSRULE.ERROR_0003_EXECUTION_FAILED"), e); //$NON-NLS-1$
275: }
276: }
277: } finally {
278: Context.exit();
279: }
280: return success;
281: }
282:
283: protected Object executeScript(ScriptableObject scriptable,
284: Scriptable scope, String script, Context cx)
285: throws Exception {
286: ScriptableObject.defineClass(scope, JavaScriptResultSet.class);
287: Set inputNames = getInputNames();
288: Iterator inputNamesIterator = inputNames.iterator();
289: String inputName;
290: Object inputValue;
291: while (inputNamesIterator.hasNext()) {
292: inputName = (String) inputNamesIterator.next();
293: if (inputName.indexOf('-') >= 0) {
294: throw new IllegalArgumentException(
295: Messages
296: .getErrorString(
297: "JSRULE.ERROR_0006_INVALID_JS_VARIABLE", inputName)); //$NON-NLS-1$
298: }
299: inputValue = getInputValue(inputName);
300: Object wrapper;
301: if (inputValue instanceof IPentahoResultSet) {
302: JavaScriptResultSet results = new JavaScriptResultSet();
303: results.setResultSet((IPentahoResultSet) inputValue);
304: wrapper = Context.javaToJS(inputValue, results);
305: } else {
306: wrapper = Context.javaToJS(inputValue, scope);
307: }
308: ScriptableObject.putProperty(scope, inputName, wrapper);
309: }
310:
311: // Add system out and this object to the scope
312: Object wrappedOut = Context.javaToJS(System.out, scope);
313: Object wrappedThis = Context.javaToJS(this , scope);
314: ScriptableObject.putProperty(scope, "out", wrappedOut); //$NON-NLS-1$
315: ScriptableObject.putProperty(scope, "rule", wrappedThis); //$NON-NLS-1$
316: // evaluate the script
317: return cx.evaluateString(scope, script, "<cmd>", 1, null); //$NON-NLS-1$
318:
319: }
320:
321: protected Object convertWrappedJavaObject(Object obj) {
322: // If we wrap an object going in, and simply return the object,
323: // without unwrapping it, we're left with an object we can't
324: // use. Case in point was a Java array going in, and being
325: // wrapped as a org.mozilla.javascript.NativeJavaArray. On
326: // the way back into the context though, it stayed a mozilla
327: // object. This unwraps objects properly so that they can be
328: // recognized throughout the system.
329: if (obj instanceof org.mozilla.javascript.NativeJavaObject) {
330: return ((org.mozilla.javascript.NativeJavaObject) obj)
331: .unwrap();
332: } else {
333: return obj;
334: }
335: }
336:
337: public boolean init() {
338: return true;
339: }
340:
341: }
|