/*
* 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 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.modules.misc.bsf;
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;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* 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;
}
}