/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License, version 2 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.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 General Public License for more details.
*
*
* Copyright 2008-2009 Pentaho Corporation. All rights reserved.
*
*/
package org.pentaho.platform.engine.services.solution;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.GenericSignatureFormatError;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.pentaho.commons.connection.IPentahoResultSet;
import org.pentaho.commons.connection.IPentahoStreamSource;
import org.pentaho.platform.api.action.IAction;
import org.pentaho.platform.api.engine.IActionParameter;
import org.pentaho.platform.api.engine.IActionSequenceResource;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.api.engine.IPluginManager;
import org.pentaho.platform.api.engine.PluginBeanException;
import org.pentaho.platform.api.repository.IContentItem;
import org.pentaho.platform.engine.core.output.SimpleContentItem;
import org.pentaho.platform.engine.core.solution.SystemSettingsParameterProvider;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.services.messages.Messages;
/**
* This class interfaces with a plain old Java object and makes it
* available as a component within the Pentaho platform.
*
* Resources and Input Parameters are set on a Pojo via setters. Any public setter
* is available to both, without bias. The setters are called individually for Resources
* and Input Parameters and as such may be called for each one should a parameter exist
* in both forms. Resources are processed first, followed by Input Parameters giving
* Input Parameters the power to override.
*
* All public getters are exposed through the PojoComponent for consumption as
* Output Parameters within an Action Sequence.
*
* There exist special methods which may be defined on a Pojo (No interface needed)
* in order to better facilitate integration to the platform. They are as follows:
* configure
* validate
* execute
* done
* getOutputs
* setResources
* setInputs
* setLogger
* setSession
* setOutputStream / getMimeType
*
* @author jamesdixon
* @deprecated Pojo components are deprecated, use {@link IAction}
*
*/
public class PojoComponent extends ComponentBase {
private static final long serialVersionUID = 7064470160805918218L;
protected Object pojo;
Map<String, Method> getMethods = new HashMap<String, Method>();
Map<String, List<Method>> setMethods = new HashMap<String, List<Method>>();
Method executeMethod = null;
Method validateMethod = null;
Method doneMethod = null;
Method resourcesMethod = null;
Method runtimeInputsMethod = null;
Method runtimeOutputsMethod = null;
Method loggerMethod = null;
Method sessionMethod = null;
Method configureMethod = null;
public Log getLogger() {
return LogFactory.getLog(PojoComponent.class);
}
@Override
public void done() {
if( doneMethod != null && pojo != null ) {
try {
doneMethod.invoke( pojo , (Object[]) null );
} catch (Exception e) {
e.printStackTrace();
}
}
}
protected void callMethod(Method method, Object value) throws Throwable {
List<Method> methods = Arrays.asList(new Method[]{method});
callMethods(methods, value);
}
protected void callMethods( List<Method> methods, Object value ) throws Throwable {
if( value instanceof String ) {
callMethodWithString( methods, value.toString() );
return;
}
boolean done = false;
for(Method method : methods){
Class<?> paramClasses[] = method.getParameterTypes();
if( paramClasses.length != 1 ) {
// we don't know how to handle this
throw new GenericSignatureFormatError();
}
Class<?> paramclass = paramClasses[0];
// do some type safety. this would be the point to do automatic type conversions
if( value instanceof IPentahoResultSet && paramclass.equals( IPentahoResultSet.class )) {
done = true;
method.invoke(pojo, new Object[] { (IPentahoResultSet) value } );
break;
}
else if( value instanceof java.lang.Boolean && ( paramclass.equals( Boolean.class ) || paramclass.equals( boolean.class ) ) ) {
done = true;
method.invoke(pojo, new Object[] { value } );
break;
}
else if( value instanceof java.lang.Integer && ( paramclass.equals( Integer.class ) || paramclass.equals( int.class ) ) ) {
done = true;
method.invoke(pojo, new Object[] { value } );
break;
}
else if( value instanceof java.lang.Long && ( paramclass.equals( Long.class ) || paramclass.equals( long.class ) ) ) {
done = true;
method.invoke(pojo, new Object[] { value } );
break;
}
else if( value instanceof java.lang.Double && ( paramclass.equals( Double.class ) || paramclass.equals( double.class ) ) ) {
done = true;
method.invoke(pojo, new Object[] { value } );
break;
}
else if( value instanceof java.lang.Float && ( paramclass.equals( Float.class ) || paramclass.equals( float.class ) ) ) {
done = true;
method.invoke(pojo, new Object[] { value } );
break;
}
else if( value instanceof IPentahoStreamSource && paramclass.equals( IPentahoStreamSource.class ) ) {
done = true;
method.invoke(pojo, new Object[] { value } );
break;
}
else if( value instanceof Date && paramclass.equals( Date.class ) ) {
done = true;
method.invoke(pojo, new Object[] { value } );
break;
}
else if( value instanceof BigDecimal && paramclass.equals( BigDecimal.class ) ) {
done = true;
method.invoke(pojo, new Object[] { value } );
break;
}
else if( value instanceof IContentItem && paramclass.equals( IContentItem.class ) ) {
done = true;
method.invoke(pojo, new Object[] { value } );
break;
}
else if( value instanceof IContentItem && paramclass.equals( String.class ) ) {
done = true;
method.invoke(pojo, new Object[] { value.toString() } );
break;
}
else if( paramclass.equals( IPentahoSession.class ) ) {
done = true;
method.invoke(pojo, new Object[] { (IPentahoSession) value } );
break;
}
else if( paramclass.equals( Log.class ) ) {
done = true;
method.invoke(pojo, new Object[] { (Log) value } );
break;
}
}
if(!done) {
// Try invoking the first instance with what we have
try{
methods.get(0).invoke(pojo, new Object[] {value});
} catch(Exception ex){
throw new IllegalArgumentException("No implementation of method \"" + Method.class.getName() + "\" takes a " + value.getClass()); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
protected void callMethodWithString( List<Method> methodList, String value ) throws Throwable {
boolean done = false;
value = applyInputsToFormat(value);
//Search ALL instances of a given method for an implementation
//that takes a single string
for(Method method : methodList){
Class<?> paramClasses[] = method.getParameterTypes();
if( paramClasses.length != 1 ) {
// we don't know how to handle this
throw new GenericSignatureFormatError();
}
Class<?> paramclass = paramClasses[0];
if( paramclass.equals( String.class ) ) {
done = true;
method.invoke(pojo, new Object[] { value } );
break;
}
}
if(!done){
for(Method method : methodList){
Class<?> paramClasses[] = method.getParameterTypes();
if( paramClasses.length != 1 ) {
// we don't know how to handle this
throw new GenericSignatureFormatError();
}
Class<?> paramclass = paramClasses[0];
if( paramclass.equals( Boolean.class ) || paramclass.equals( boolean.class ) ) {
done = true;
method.invoke(pojo, new Object[] { new Boolean( value ) } );
break;
}
else if( paramclass.equals( Integer.class ) || paramclass.equals( int.class )) {
done = true;
method.invoke(pojo, new Object[] { new Integer( value ) } );
break;
}
else if( paramclass.equals( Long.class ) || paramclass.equals( long.class )) {
done = true;
method.invoke(pojo, new Object[] { new Long( value ) } );
break;
}
else if( paramclass.equals( Double.class ) || paramclass.equals( double.class )) {
done = true;
method.invoke(pojo, new Object[] { new Double( value ) } );
break;
}
else if( paramclass.equals( Float.class ) || paramclass.equals( float.class )) {
done = true;
method.invoke(pojo, new Object[] { new Float( value ) } );
break;
}
else if( paramclass.equals( BigDecimal.class ) ) {
done = true;
method.invoke(pojo, new Object[] { new BigDecimal( value ) } );
break;
}
}
}
if(!done){
throw new GenericSignatureFormatError();
}
}
@SuppressWarnings({"unchecked"})
@Override
protected boolean executeAction() throws Throwable {
Set<?> inputNames = getInputNames();
Element defnNode = (Element) getComponentDefinition();
// first do the system settings so that component settings and inputs can override them if necessary
// if( pojo instanceof IConfiguredPojo ) {
if( getMethods.containsKey("CONFIGSETTINGSPATHS") && configureMethod != null ) { //$NON-NLS-1$
Method method = getMethods.get( "CONFIGSETTINGSPATHS" ); //$NON-NLS-1$
Set<String> settingsPaths = (Set<String>) method.invoke( pojo , new Object[] {} );
Iterator<String> keys = settingsPaths.iterator();
Map<String,String> settings = new HashMap<String,String>();
SystemSettingsParameterProvider params = new SystemSettingsParameterProvider();
while( keys.hasNext() ) {
String path = keys.next();
String value = params.getStringParameter( path, null );
if( value != null ) {
settings.put( path, value );
}
}
configureMethod.invoke( pojo , new Object[] { settings } );
}
// set the PentahoSession
if( sessionMethod != null ) {
callMethods( Arrays.asList(new Method[]{sessionMethod}), getSession() );
}
// set the logger
if( loggerMethod != null ) {
callMethods( Arrays.asList(new Method[]{loggerMethod}), getLogger() );
}
Map<String,Object> inputMap = new HashMap<String,Object>();
// look at the component settings
List<?> nodes = defnNode.selectNodes( "*" ); //$NON-NLS-1$
for( int idx=0; idx<nodes.size(); idx++ ) {
Element node = (Element) nodes.get( idx );
// inputs may typically contain a dash in them, such as
// something like "report-definition" and we should expect
// a setter as setReportDefinition, so we will remove the
// dashes and everything should proceed as expected
String name = node.getName().replace("-", "").toUpperCase(); //$NON-NLS-1$ //$NON-NLS-2$
if( !name.equals( "CLASS" ) && !name.equals( "OUTPUTSTREAM" )) { //$NON-NLS-1$ //$NON-NLS-2$
String value = node.getText();
List<Method> method = setMethods.get( name );
if( method != null ) {
callMethodWithString( method, value );
}
else if( runtimeInputsMethod != null ) {
inputMap.put(name, value);
} else {
// Supress error (For string/value replacement)
getLogger().warn(Messages.getInstance().getString("PojoComponent.UNUSED_INPUT", name)); //$NON-NLS-1$
}
}
}
Iterator<?> it = null;
// now process all of the resources and see if we can call them as setters
Set<?> resourceNames = getResourceNames();
Map<String, IActionSequenceResource> resourceMap = new HashMap<String, IActionSequenceResource>();
if( resourceNames!= null && resourceNames.size() > 0 ) {
it = resourceNames.iterator();
while( it.hasNext() ) {
String name = (String) it.next();
IActionSequenceResource resource = getResource( name );
name = name.replace("-", ""); //$NON-NLS-1$ //$NON-NLS-2$
resourceMap.put(name, resource);
List<Method> methods = setMethods.get( name.toUpperCase() );
if( methods != null ) {
for(Method method : methods){
Class<?>[] paramTypes = method.getParameterTypes();
if(paramTypes.length == 1){
Object value = null;
if(paramTypes[0] == InputStream.class ) {
value = getRuntimeContext().getResourceInputStream( resource );
} else if(paramTypes[0] == IActionSequenceResource.class ) {
value = resource;
} else if(paramTypes[0] == String.class){
value = getRuntimeContext().getResourceAsString(resource);
} else if(paramTypes[0] == Document.class){
value = getRuntimeContext().getResourceAsDocument(resource);
}
callMethod(method, value);
}
}
} else {
// BISERVER-2715 we should ignore this as the resource might be meant for another component
}
}
}
// now process all of the inputs, overriding the component settings
it = inputNames.iterator();
while( it.hasNext() ) {
String name = (String) it.next();
Object value = getInputValue( name );
// now that we have the value, we can fix the name
name = name.replace("-", ""); //$NON-NLS-1$ //$NON-NLS-2$
List<Method> methods = setMethods.get( name.toUpperCase() );
if( methods != null ) {
callMethods( methods, value );
}
else if( runtimeInputsMethod != null ) {
inputMap.put(name, value);
} else {
// Supress error (For string/value replacement)
getLogger().warn(Messages.getInstance().getString("PojoComponent.UNUSED_INPUT", name)); //$NON-NLS-1$
}
}
if ( resourceMap.size() > 0 && resourcesMethod != null ) {
// call the resources setter
resourcesMethod.invoke( pojo , new Object[] { resourceMap } );
}
if( inputMap.size() > 0 && runtimeInputsMethod != null ) {
// call the generic input setter
runtimeInputsMethod.invoke( pojo , new Object[] { inputMap } );
}
if( getOutputNames().contains( "outputstream" ) && setMethods.containsKey( "OUTPUTSTREAM" ) //$NON-NLS-1$ //$NON-NLS-2$
&& getMethods.containsKey( "MIMETYPE" ) ) { //$NON-NLS-1$
// get the mime-type
//Get the first method to match
Method method = getMethods.get( "MIMETYPE" ); //$NON-NLS-1$
String mimeType = (String) method.invoke( pojo , new Object[] {} );
String mappedOutputName = "outputstream"; //$NON-NLS-1$
if ((getActionDefinition() != null) && (getActionDefinition().getOutput("outputstream") != null)) { //$NON-NLS-1$
mappedOutputName = getActionDefinition().getOutput("outputstream").getPublicName(); //$NON-NLS-1$
}
//this marks the HttpOutputHandler as contentDone=true, causing the MessageFormatter to not print an error
IContentItem contentItem = getOutputContentItem(mappedOutputName, mimeType );
if (! (contentItem instanceof SimpleContentItem) ) {
// SimpleContentItem can't handle being added to outputs because it
// doesn't have a getInputStream(), and the path used to return
// null.
setOutputValue("outputstream", contentItem); //$NON-NLS-1$
}
// set the output stream
OutputStream out = contentItem.getOutputStream( getActionName() );
method = setMethods.get( "OUTPUTSTREAM" ).get(0); //$NON-NLS-1$
method.invoke(pojo, new Object[] {out});
}
if( validateMethod != null ) {
Object obj = validateMethod.invoke( pojo, (Object[]) null );
if( obj instanceof Boolean ) {
Boolean ok = (Boolean) obj;
if( !ok ) {
return false;
}
}
}
// now execute the pojo
Boolean result = Boolean.FALSE;
if( executeMethod != null ) {
result = (Boolean) executeMethod.invoke( pojo, new Object[] {} );
} else {
// we can only assume we are ok so far
result = Boolean.TRUE;
}
// now handle outputs
Set<?> outputNames = getOutputNames();
// first get the runtime outputs
Map<String,Object> outputMap = new HashMap<String,Object>();
if( runtimeOutputsMethod != null ) {
outputMap = (Map<String,Object>) runtimeOutputsMethod.invoke( pojo, new Object[] {} );
}
if( outputMap.size() > 0 ) {
}
it = outputNames.iterator();
while( it.hasNext() ) {
String name = (String) it.next();
if( name.equals( "outputstream" ) ) { //$NON-NLS-1$
// we should be done
} else {
IActionParameter param = getOutputItem( name );
Method method = getMethods.get( name.toUpperCase() );
if( method != null ) {
Object value = method.invoke(pojo, new Object[] { } );
param.setValue( value );
} else {
Object value = outputMap.get( name );
if( value != null ) {
param.setValue( value );
} else {
throw new NoSuchMethodException( name );
}
}
}
}
return result.booleanValue();
}
@Override
public boolean init() {
// nothing to do here
return true;
}
@Override
protected boolean validateAction() {
boolean ok = false;
if( pojo == null && isDefinedInput( "class" ) ) { //$NON-NLS-1$
String className = getInputStringValue( "class" ); //$NON-NLS-1$
//try to load the class from a plugin
IPluginManager pluginMgr = PentahoSystem.get(IPluginManager.class, getSession());
if(pluginMgr != null && pluginMgr.isBeanRegistered(className)) {
try {
pojo = pluginMgr.getBean(className); //"className" is actually the plugin bean id in this case
} catch (PluginBeanException e) {
error( "Could not load bean class from plugin" , e); //$NON-NLS-1$
return false;
}
}
//the bean class was not found in a plugin, so try the default classloader
if (pojo == null){
try {
// TODO support loading classes from the solution repository
Class<?> aClass = getClass().getClassLoader().loadClass(className);
pojo = aClass.newInstance();
} catch (Exception ex) {
error( "Could not load bean class" , ex); //$NON-NLS-1$
return false;
}
}
}
if( pojo != null ) {
// By the time we get here, we've got our class
try {
Method methods[] = pojo.getClass().getMethods();
// create a method map
for( Method method : methods ) {
String name = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
if( name.equals( "getOutputs" ) ) { //$NON-NLS-1$
runtimeOutputsMethod = method;
}
else if( name.equals( "setInputs" ) ) { //$NON-NLS-1$
runtimeInputsMethod = method;
}
else if( name.equals( "setResources" ) ) { //$NON-NLS-1$
resourcesMethod = method;
}
else if( name.equals( "setLogger" ) ) { //$NON-NLS-1$
if( paramTypes.length == 1 && paramTypes[0] == Log.class ) {
loggerMethod = method;
}
}
else if( name.equals( "setSession" ) ) { //$NON-NLS-1$
if( paramTypes.length == 1 && paramTypes[0] == IPentahoSession.class ) {
sessionMethod = method;
}
}
else if( name.equalsIgnoreCase( "configure" ) ) { //$NON-NLS-1$
configureMethod = method;
}
else if( name.startsWith( "set" ) ) { //$NON-NLS-1$
name = name.substring( 3 ).toUpperCase();
if (name.equals("CLASS")) { //$NON-NLS-1$
warn(Messages.getInstance().getString("PojoComponent.CANNOT_USE_SETCLASS")); //$NON-NLS-1$
} else {
if(!setMethods.containsKey(name)){
setMethods.put(name, new ArrayList<Method>());
}
setMethods.get(name).add(method);
}
}
else if( name.startsWith( "get" ) ) { //$NON-NLS-1$
name = name.substring( 3 ).toUpperCase();
getMethods.put(name, method );
}
else if( name.equalsIgnoreCase( "execute" ) ) { //$NON-NLS-1$
executeMethod = method;
}
else if( name.equalsIgnoreCase( "validate" ) ) { //$NON-NLS-1$
validateMethod = method;
}
else if( name.equalsIgnoreCase( "done" ) ) { //$NON-NLS-1$
doneMethod = method;
}
}
ok = true;
} catch (Throwable ex) {
error( "Could not load object class" , ex); //$NON-NLS-1$
}
}
return ok;
}
@Override
protected boolean validateSystemSettings() {
// nothing to do here, the pojo must do this during its init
return true;
}
public void setPojo(Object pojo) {
this.pojo = pojo;
}
}