/* * 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; } }