org.pentaho.platform.engine.services.solution.PojoComponent.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.platform.engine.services.solution.PojoComponent.java

Source

/*
 * 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() //$NON-NLS-1$
                        + "\" takes a " + value.getClass()); //$NON-NLS-1$
            }
        }
    }

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

}