/*
* 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 (c) 2001 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.modules.misc.bsf;
import java.io.IOException;
import java.io.ObjectInputStream;
import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.DataRow;
import org.pentaho.reporting.engine.classic.core.function.AbstractExpression;
import org.pentaho.reporting.engine.classic.core.function.ExpressionRuntime;
import org.pentaho.reporting.engine.classic.core.function.WrapperExpressionRuntime;
import org.pentaho.reporting.engine.classic.core.states.LegacyDataRowWrapper;
/**
* An expression that uses the Bean scripting framework to perform a scripted calculation.
*
* @author Thomas Morgner
*/
public class BSFExpression extends AbstractExpression
{
private static final Log logger = LogFactory.getLog(BSFExpression.class);
/**
* The interpreter used to evaluate the expression.
*/
private transient BSFManager interpreter;
private transient boolean invalid;
private transient LegacyDataRowWrapper dataRowWrapper;
private transient WrapperExpressionRuntime runtimeWrapper;
private String language;
private String script;
private String expression;
/**
* Default constructor, create a new BeanShellExpression.
*/
public BSFExpression()
{
}
/**
* Creates a new interpreter instance.
*
* @return the interpreter or null, if there was an error.
*/
protected BSFManager createInterpreter()
{
try
{
final BSFManager interpreter = new BSFManager();
initializeInterpreter(interpreter);
return interpreter;
}
catch (Exception e)
{
BSFExpression.logger.error("Unable to initialize the expression", e); //$NON-NLS-1$
return null;
}
}
/**
* Initializes the Bean-Scripting Framework manager.
*
* @param interpreter the BSF-Manager that should be initialized.
* @throws BSFException if an error occured.
*/
protected void initializeInterpreter(final BSFManager interpreter)
throws BSFException
{
dataRowWrapper = new LegacyDataRowWrapper();
runtimeWrapper = new WrapperExpressionRuntime();
runtimeWrapper.update(getDataRow(), getRuntime());
interpreter.declareBean("runtime", runtimeWrapper, ExpressionRuntime.class); //$NON-NLS-1$
interpreter.declareBean("dataRow", dataRowWrapper, DataRow.class); //$NON-NLS-1$
if (script != null)
{
interpreter.exec(getLanguage(), "script", 1, 1, getScript()); //$NON-NLS-1$
}
}
/**
* Evaluates the defined expression. If an exception or an evaluation error occures, the evaluation returns null and
* the error is logged. The current datarow and a copy of the expressions properties are set to script-internal
* variables. Changes to the properties will not alter the expressions original properties and will be lost when the
* evaluation is finished.
* <p/>
* Expressions do not maintain a state and no assumptions about the order of evaluation can be made.
*
* @return the evaluated value or null.
*/
public Object getValue()
{
if (invalid || expression == null)
{
return null;
}
if (interpreter == null)
{
interpreter = createInterpreter();
if (interpreter == null)
{
invalid = true;
return null;
}
}
try
{
runtimeWrapper.update(null, getRuntime());
dataRowWrapper.setParent(getDataRow());
return interpreter.eval
(getLanguage(), "expression", 1, 1, getExpression()); //$NON-NLS-1$
}
catch (Exception e)
{
BSFExpression.logger.warn("Evaluation error: " + //$NON-NLS-1$
e.getClass() + " - " + e.getMessage(), e); //$NON-NLS-1$
return null;
}
finally
{
runtimeWrapper.update(null, null);
dataRowWrapper.setParent(null);
}
}
/**
* Clones the expression and reinitializes the script.
*
* @return a clone of the expression.
* @throws CloneNotSupportedException this should never happen.
*/
public Object clone()
throws CloneNotSupportedException
{
final BSFExpression expression = (BSFExpression) super.clone();
expression.interpreter = null;
return expression;
}
/**
* Serialisation support. The transient child elements were not saved.
*
* @param in the input stream.
* @throws IOException if there is an I/O error.
* @throws ClassNotFoundException if a serialized class is not defined on this system.
*/
private void readObject(final ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
}
/**
* Returns the script that gets evaluated every time the getValue() method is called.
*
* @return the script.
*/
public String getExpression()
{
return expression;
}
/**
* Invalidates the interpreter-cache and forces a reinterpretation of the script.
*/
protected void invalidate()
{
this.interpreter = null;
}
/**
* Sets the script that should be executed. Whats in the script depends on what langage is selected.
*
* @param expression the script.
*/
public void setExpression(final String expression)
{
this.expression = expression;
this.interpreter = null;
}
/**
* Returns the programming language, in which the interpreter work.
*
* @return the programming language, which must be one of the supported BSF-Languages.
*/
public String getLanguage()
{
return language;
}
/**
* Defines the programming language of the script and expression.
*
* @param language the programming language of the script.
*/
public void setLanguage(final String language)
{
this.language = language;
this.interpreter = null;
}
/**
* Returns the script. The script is a predefined piece of code that gets executed once. It can (and should) be used
* to perform global initializations and to define functions.
*
* @return the script (can be null).
*/
public String getScript()
{
return script;
}
/**
* Defines the script. The script is a predefined piece of code that gets executed once. It can (and should) be used
* to perform global initializations and to define functions.
*
* @param script an initialization script.
*/
public void setScript(final String script)
{
this.script = script;
this.interpreter = null;
}
}