/*
* 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 2006 - 2013 Pentaho Corporation. All rights reserved.
*/
package org.pentaho.platform.engine.services.solution;
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.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.api.repository2.unified.RepositoryFilePermission;
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;
import org.pentaho.platform.util.messages.LocaleHelper;
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;
/**
* 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 = resource.getInputStream( RepositoryFilePermission.READ, LocaleHelper.getLocale() );
} 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 );
}
}
//CHECKSTYLE IGNORE EmptyBlock FOR NEXT 3 LINES
} 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[] {} );
}
it = outputNames.iterator();
while ( it.hasNext() ) {
String name = (String) it.next();
//CHECKSTYLE IGNORE EmptyBlock FOR NEXT 3 LINES
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;
}
}