/*
* Copyright (c) 2006 Stiftung Deutsches Elektronen-Synchroton,
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY.
*
* THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS.
* WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND
* NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
* THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE
* IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR
* CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
* NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
* DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
* OR MODIFICATIONS.
* THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION,
* USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS
* PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY
* AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM
*/
package org.csstudio.sds.internal.model.logic;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import org.csstudio.sds.internal.rules.LogicException;
import org.csstudio.sds.model.IRule;
import org.csstudio.sds.model.PropertyTypesEnum;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextAction;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeJavaObject;
import org.mozilla.javascript.Scriptable;
/**
* A rule that is based upon a script definition.
*
* @author Alexander Will
* @version $Revision: 1.9 $
*
*/
public class ScriptedRule implements IRule {
/**
* The name of the script function that will be executed.
*/
private static final String SCRIPT_LOGIC_FUNCTION_NAME = "execute"; //$NON-NLS-1$
/**
* The name of the script field that contains the textual description of the
* script.
*/
private static final String SCRIPT_DESCRIPTION = "description"; //$NON-NLS-1$
/**
* The name of the script field that contains the descriptions of the
* script's parameters.
*/
private static final String SCRIPT_PARAMTER_DESCRIPTIONS = "parameters"; //$NON-NLS-1$
/**
* The name of the script field that contains the descriptions of the
* script's return value.
*/
private static final String SCRIPT_PROPERTY_TYPES = "compatibleProperties";
/**
* The script function that will be executed.
*/
private Function _scriptFunction;
/**
* A textual description of this rule.
*/
private String _description;
/**
* The ID of this rule.
*/
private String _id;
/**
* The descriptions of the parameters that are defined within the underlying
* script.
*/
private List<String> _parameterDescriptions;
/**
* The scripting scope.
*/
private Scriptable _scriptScope;
/**
* The expected return type.
*/
private PropertyTypesEnum[] _compatiblePropertyTypes;
/**
* Standard constructor.
*
* @param id
* The ID of this rule.
* @param scriptFileInputStream
* An <code>InputStream</code> that contains the script that will
* be associated to rule.
* @throws LogicException
* A <code>LogicException</code> is thrown an error occurs
* during the parsing of the script.
*/
public ScriptedRule(final String id, final InputStream scriptFileInputStream)
throws LogicException {
_description = null;
_parameterDescriptions = new ArrayList<String>();
_id = id;
try {
Context scriptContext = Context.enter();
_scriptScope = new ImporterTopLevel(scriptContext);
_scriptFunction = parseScriptFile(scriptFileInputStream,
scriptContext);
} catch (Exception e) {
throw new LogicException(
"Script " + id + ": Error parsing script", e); //$NON-NLS-1$ //$NON-NLS-2$
} finally {
Context.exit();
}
if (_scriptFunction == null) {
throw new LogicException(
"Script " + id + ": " + SCRIPT_LOGIC_FUNCTION_NAME + " is undefined!"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
if (_description == null) {
throw new LogicException("Script " + id //$NON-NLS-1$
+ ": No textual description was defined!"); //$NON-NLS-1$
}
}
/**
* {@inheritDoc}
*/
@Override
public final synchronized Object evaluate(final Object[] arguments) {
Object result = Context.call(new ContextAction() {
/**
* {@inheritDoc}
*/
@Override
public Object run(final Context cx) {
Object scriptResult = null;
Object jsObject = Context.javaToJS(arguments, _scriptScope);
Object callResult = _scriptFunction.call(cx, _scriptScope,
_scriptScope, new Object[] { jsObject });
if (callResult instanceof NativeJavaObject) {
NativeJavaObject njo = (NativeJavaObject) callResult;
scriptResult = njo.unwrap();
} else if (callResult != null) {
scriptResult = callResult;
}
return scriptResult;
}
});
return result;
}
/**
* Returns the expected return type.
*
* @return The expected return type
*/
public PropertyTypesEnum[] getCompatiblePropertyTypes() {
return _compatiblePropertyTypes;
}
/**
* Return the textual description of this rule.
*
* @return The textual description of this rule.
*/
@Override
public final String getDescription() {
return _description;
}
/**
* Return the ID of this rule.
*
* @return The ID of this rule.
*/
public final String getId() {
return _id;
}
/**
* Return the descriptions of the parameters that are defined within the
* underlying script.
*
* @return The descriptions of the parameters that are defined within the
* underlying script.
*/
public final String[] getParameterDescriptions() {
return _parameterDescriptions.toArray(new String[_parameterDescriptions
.size()]);
}
/**
* Parse the given script file and try to allocate the "doLogic" function.
*
* @param scriptFileInputStream
* The input stream that contains the script
* @param scriptContext
* The script context that is used to initially parse the script
* file.
* @return The "doLogic" function or null if it is not defined within the
* given script.
* @throws IOException
* If an IO error occurs while the script file is parsed.
*/
private Function parseScriptFile(final InputStream scriptFileInputStream,
final Context scriptContext) throws IOException {
Function result = null;
String scriptString = ""; //$NON-NLS-1$
BufferedReader reader = new BufferedReader(new InputStreamReader(
scriptFileInputStream));
while (reader.ready()) {
scriptString += reader.readLine();
}
reader.close();
// Evaluate the script string. Ignore the result. This is needed to
// allocate the proper function object.
scriptContext.evaluateString(_scriptScope, scriptString,
"script file", 1, null); //$NON-NLS-1$
_compatiblePropertyTypes = fetchCompatiblePropertyTypes();
_description = fetchTextualDescription();
_parameterDescriptions = fetchParameterDescriptions();
// Try to allocate the function object.
Object functionObject = _scriptScope.get(SCRIPT_LOGIC_FUNCTION_NAME,
_scriptScope);
if (functionObject instanceof Function) {
result = (Function) functionObject;
}
return result;
}
/**
* Try to fetch the return type from the script.
*/
private PropertyTypesEnum[] fetchCompatiblePropertyTypes() {
List<PropertyTypesEnum> result = new ArrayList<PropertyTypesEnum>();
Object returnTypeObject = _scriptScope.get(SCRIPT_PROPERTY_TYPES,
_scriptScope);
if (returnTypeObject != Scriptable.NOT_FOUND) {
String attribute = Context.toString(returnTypeObject);
if (attribute != null) {
String[] ids = attribute.split(",");
for (String id : ids) {
try {
result.add(PropertyTypesEnum.createFromPortable(id
.trim()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return result.toArray(new PropertyTypesEnum[result.size()]);
}
/**
* Try to fetch the textual description from the script.
*/
private String fetchTextualDescription() {
String result = "";
Object descriptionObject = _scriptScope.get(SCRIPT_DESCRIPTION,
_scriptScope);
if (descriptionObject != Scriptable.NOT_FOUND) {
result = Context.toString(descriptionObject);
}
return result;
}
/**
* Try to fetch the parameter descriptions from the script.
*/
private List<String> fetchParameterDescriptions() {
List<String> result = new ArrayList<String>();
Object parameterDescriptionsObject = _scriptScope.get(
SCRIPT_PARAMTER_DESCRIPTIONS, _scriptScope);
if ((parameterDescriptionsObject instanceof NativeArray)) {
NativeArray parameterDescriptions = (NativeArray) parameterDescriptionsObject;
for (int i = 0; i < parameterDescriptions.getIds().length; i++) {
Object o = parameterDescriptions.get(i, parameterDescriptions);
if (o != null) {
result.add(o.toString());
}
}
}
return result;
}
}