org.pepstock.jem.jbpm.tasks.JemWorkItemHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.pepstock.jem.jbpm.tasks.JemWorkItemHandler.java

Source

/**
JEM, the BEE - Job Entry Manager, the Batch Execution Environment
Copyright (C) 2012-2015   Andrea "Stock" Stocchero
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
    
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.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.pepstock.jem.jbpm.tasks;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;

import org.apache.commons.lang.StringUtils;
import org.apache.tools.ant.BuildException;
import org.kie.api.runtime.process.WorkItem;
import org.kie.api.runtime.process.WorkItemHandler;
import org.kie.api.runtime.process.WorkItemManager;
import org.pepstock.catalog.DataDescriptionImpl;
import org.pepstock.catalog.gdg.GDGManager;
import org.pepstock.jem.Result;
import org.pepstock.jem.annotations.ToBeExecuted;
import org.pepstock.jem.jbpm.JBpmKeys;
import org.pepstock.jem.jbpm.JBpmMessage;
import org.pepstock.jem.jbpm.Task;
import org.pepstock.jem.jbpm.tasks.workitems.CustomMethodWorkItem;
import org.pepstock.jem.jbpm.tasks.workitems.DelegatedWorkItem;
import org.pepstock.jem.jbpm.tasks.workitems.MainClassWorkItem;
import org.pepstock.jem.log.JemException;
import org.pepstock.jem.log.JemRuntimeException;
import org.pepstock.jem.log.LogAppl;
import org.pepstock.jem.log.MessageException;
import org.pepstock.jem.node.DataPathsContainer;
import org.pepstock.jem.node.configuration.ConfigKeys;
import org.pepstock.jem.node.resources.Resource;
import org.pepstock.jem.node.resources.ResourceLoaderReference;
import org.pepstock.jem.node.resources.ResourcePropertiesUtil;
import org.pepstock.jem.node.resources.ResourceProperty;
import org.pepstock.jem.node.resources.impl.CommonKeys;
import org.pepstock.jem.node.rmi.CommonResourcer;
import org.pepstock.jem.node.sgm.InvalidDatasetNameException;
import org.pepstock.jem.node.sgm.PathsContainer;
import org.pepstock.jem.node.tasks.InitiatorManager;
import org.pepstock.jem.node.tasks.JobId;
import org.pepstock.jem.node.tasks.jndi.ContextUtils;
import org.pepstock.jem.node.tasks.jndi.DataPathsReference;
import org.pepstock.jem.node.tasks.jndi.DataStreamReference;
import org.pepstock.jem.node.tasks.jndi.StringRefAddrKeys;
import org.pepstock.jem.util.VariableSubstituter;

import com.thoughtworks.xstream.XStream;

/**
 * Is a work item of JBPM which is able to call different kinds of JAVA class, managing their execution.<br>
 * Is able to call:<br>
 * <ul>
 * <li> Instance of <code>JemWorkItem</code> interface
 * <li> Main java class
 * <li> Any method of any class but with specific signatures
 * </ul>
 * It creates all JEM structures (data descriptions, data sources, locks) all via JNDI for the executed classes.
 * 
 * @see CustomMethodWorkItem   
 * @author Andrea "Stock" Stocchero
 * @version 2.2
 */
public class JemWorkItemHandler implements WorkItemHandler {

    private static final String METHOD_DELIMITER = "#";

    private static final String CLASS_NAME_KEY = "jem.workItem.className";

    private static final String RESULT_KEY = "jem.workItem.result";

    /* (non-Javadoc)
     * @see org.kie.api.runtime.process.WorkItemHandler#abortWorkItem(org.kie.api.runtime.process.WorkItem, org.kie.api.runtime.process.WorkItemManager)
     */
    @Override
    public void abortWorkItem(WorkItem workItem, WorkItemManager manager) {
        // Do nothing
    }

    /* (non-Javadoc)
     * @see org.kie.api.runtime.process.WorkItemHandler#executeWorkItem(org.kie.api.runtime.process.WorkItem, org.kie.api.runtime.process.WorkItemManager)
     */
    @Override
    public void executeWorkItem(WorkItem workItem, WorkItemManager manager) {
        // gets the java class for workitem
        String className = (String) workItem.getParameter(CLASS_NAME_KEY);
        // must be defined
        if (className == null) {
            throw new JemRuntimeException(JBpmMessage.JEMM001E.toMessage().getFormattedMessage(CLASS_NAME_KEY));
        }
        // removes all blank 
        className = className.trim();
        // if it still has got blanks, exception
        if (className.contains(" ")) {
            throw new JemRuntimeException(JBpmMessage.JEMM056E.toMessage().getFormattedMessage(className));
        }
        // loads parameters as list of properties for substitutions
        JobsProperties.getInstance().loadParameters(workItem.getParameters());

        // checks if there a method to call
        String methodName = null;
        if (className.contains(METHOD_DELIMITER)) {
            methodName = StringUtils.substringAfter(className, METHOD_DELIMITER);
            className = StringUtils.substringBefore(className, METHOD_DELIMITER);
        }
        // sets security manager internal action
        JBpmBatchSecurityManager batchSM = (JBpmBatchSecurityManager) System.getSecurityManager();
        batchSM.setInternalAction(true);

        // defines the wrapper
        JemWorkItem wrapper = null;

        // load class
        Class<?> clazz;
        try {
            clazz = Class.forName(className);
            // if not method has been set (and ONLY if there isn't) 
            // scans method to see if there the annotation
            // on the method to call
            if (methodName == null) {
                for (Method m : clazz.getDeclaredMethods()) {
                    // checks if there is the annotation
                    // and not already set
                    if (m.isAnnotationPresent(ToBeExecuted.class) && methodName == null) {
                        //set method name
                        methodName = m.getName();
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            batchSM.setInternalAction(false);
            LogAppl.getInstance().emit(JBpmMessage.JEMM006E, e, className);
            throw new JemRuntimeException(JBpmMessage.JEMM006E.toMessage().getFormattedMessage(className), e);
        }
        // if it has got the method instantiated
        if (methodName != null) {
            try {
                wrapper = new CustomMethodWorkItem(clazz, methodName);
            } catch (InstantiationException e) {
                batchSM.setInternalAction(false);
                LogAppl.getInstance().emit(JBpmMessage.JEMM006E, e, className);
                throw new JemRuntimeException(JBpmMessage.JEMM006E.toMessage().getFormattedMessage(className), e);
            } catch (IllegalAccessException e) {
                batchSM.setInternalAction(false);
                LogAppl.getInstance().emit(JBpmMessage.JEMM006E, e, className);
                throw new JemRuntimeException(JBpmMessage.JEMM006E.toMessage().getFormattedMessage(className), e);
            }
        } else if (MainClassWorkItem.hasMainMethod(clazz)) {
            wrapper = new MainClassWorkItem(clazz);
        } else {
            try {
                // load by Class.forName of workItem
                Object instance = clazz.newInstance();
                // check if it's a JemWorkItem. if not,
                // exception occurs. 
                if (instance instanceof JemWorkItem) {
                    wrapper = new DelegatedWorkItem(instance);
                } else {
                    batchSM.setInternalAction(false);
                    LogAppl.getInstance().emit(JBpmMessage.JEMM004E, className);
                    throw new JemRuntimeException(JBpmMessage.JEMM004E.toMessage().getFormattedMessage(className));
                }
            } catch (InstantiationException e) {
                batchSM.setInternalAction(false);
                LogAppl.getInstance().emit(JBpmMessage.JEMM006E, e, className);
                throw new JemRuntimeException(JBpmMessage.JEMM006E.toMessage().getFormattedMessage(className), e);
            } catch (IllegalAccessException e) {
                batchSM.setInternalAction(false);
                LogAppl.getInstance().emit(JBpmMessage.JEMM006E, e, className);
                throw new JemRuntimeException(JBpmMessage.JEMM006E.toMessage().getFormattedMessage(className), e);
            }
        }

        // gets the current task
        Task currentTask = CompleteTasksList.getInstance().getTaskByWorkItemID(workItem.getId());
        if (currentTask == null) {
            batchSM.setInternalAction(false);
            throw new JemRuntimeException(JBpmMessage.JEMM002E.toMessage().getFormattedMessage(workItem.getId()));
        }
        // initialize the listener passing parameters
        // properties
        try {
            int returnCode = execute(currentTask, wrapper, workItem.getParameters());
            LogAppl.getInstance().emit(JBpmMessage.JEMM057I, currentTask.getId(), returnCode);
            currentTask.setReturnCode(returnCode);
            Map<String, Object> output = new HashMap<String, Object>();
            output.put(RESULT_KEY, returnCode);
            manager.completeWorkItem(workItem.getId(), output);
        } catch (Exception e) {
            currentTask.setReturnCode(Result.ERROR);
            throw new JemRuntimeException(e);
        }
    }

    /**
     * Executes the work item, creating all JEM features and therefore reachable by JNDI.
     * 
     * @param task current JBPM task
     * @param item sub workitem to execute
     * @param parms list of parameters, created by JBPM for this work item
     * @return return code of work item execution
     * @throws JemException if any error occurs
     */
    private int execute(Task task, JemWorkItem item, Map<String, Object> parms) throws JemException {
        // this boolean is necessary to understand if I have an exception 
        // before calling the main class
        boolean isExecutionStarted = false;

        JBpmBatchSecurityManager batchSM = (JBpmBatchSecurityManager) System.getSecurityManager();

        // object serializer and deserializer into XML
        XStream xstream = new XStream();

        List<DataDescriptionImpl> ddList = null;
        InitialContext ic = null;

        try {
            // gets all data description requested by this task
            ddList = ImplementationsContainer.getInstance().getDataDescriptionsByItem(task);
            // new intial context for JNDI
            ic = ContextUtils.getContext();

            // LOADS DataPaths Container
            Reference referencePaths = new DataPathsReference();
            // loads dataPaths on static name
            String xmlPaths = xstream.toXML(DataPathsContainer.getInstance());
            // adds the String into a data stream reference
            referencePaths.add(new StringRefAddr(StringRefAddrKeys.DATAPATHS_KEY, xmlPaths));
            // re-bind the object inside the JNDI context
            ic.rebind(JBpmKeys.JBPM_DATAPATHS_BIND_NAME, referencePaths);

            // scans all datasource passed
            for (DataSource source : task.getDataSources()) {
                // checks if datasource is well defined
                if (source.getResource() == null) {
                    throw new MessageException(JBpmMessage.JEMM027E);
                } else if (source.getName() == null) {
                    // if name is missing, it uses the same string 
                    // used to define the resource
                    source.setName(source.getResource());
                }

                // gets the RMi object to get resources
                CommonResourcer resourcer = InitiatorManager.getCommonResourcer();
                // lookups by RMI for the database 
                Resource res = resourcer.lookup(JobId.VALUE, source.getResource());
                if (!batchSM.checkResource(res)) {
                    throw new MessageException(JBpmMessage.JEMM028E, res.toString());
                }

                // all properties create all StringRefAddrs necessary  
                Map<String, ResourceProperty> properties = res.getProperties();
                // scans all properteis set by JCL
                for (Property property : source.getProperties()) {
                    if (property.isCustom()) {
                        if (res.getCustomProperties() == null) {
                            res.setCustomProperties(new HashMap<String, String>());
                        }
                        if (!res.getCustomProperties().containsKey(property.getName())) {
                            res.getCustomProperties().put(property.getName(), property.getText().toString());
                        } else {
                            throw new MessageException(JBpmMessage.JEMM028E, property.getName(), res);
                        }
                    } else {
                        // if a key is defined FINAL, throw an exception
                        for (ResourceProperty resProperty : properties.values()) {
                            if (resProperty.getName().equalsIgnoreCase(property.getName())
                                    && !resProperty.isOverride()) {
                                throw new MessageException(JBpmMessage.JEMM028E, property.getName(), res);
                            }
                        }
                        ResourcePropertiesUtil.addProperty(res, property.getName(), property.getText().toString());
                    }
                }

                // creates a JNDI reference
                Reference ref = getReference(resourcer, res, source, ddList);

                // loads all properties into RefAddr
                for (ResourceProperty property : properties.values()) {
                    ref.add(new StringRefAddr(property.getName(),
                            replaceProperties(property.getValue(), JobsProperties.getInstance().getProperties())));
                }

                // loads custom properties in a string format
                if (res.getCustomProperties() != null && !res.getCustomProperties().isEmpty()) {
                    // loads all entries and substitute variables
                    for (Entry<String, String> entry : res.getCustomProperties().entrySet()) {
                        String value = replaceProperties(entry.getValue(),
                                JobsProperties.getInstance().getProperties());
                        entry.setValue(value);
                    }
                    // adds to reference
                    ref.add(new StringRefAddr(CommonKeys.RESOURCE_CUSTOM_PROPERTIES,
                            res.getCustomPropertiesString()));
                }

                // binds the object with format [type]/[name]
                LogAppl.getInstance().emit(JBpmMessage.JEMM035I, res);
                ic.rebind(source.getName(), ref);
            }

            // if list of data description is empty, go to execute java main
            // class
            if (!ddList.isEmpty()) {

                // after locking, checks for GDG
                // is sure here the root (is a properties file) of GDG is locked
                // (doesn't matter if in READ or WRITE)
                // so can read a consistent data from root and gets the right
                // generation
                // starting from relative position
                for (DataDescriptionImpl ddImpl : ddList) {
                    // creates a reference, accessible by name. Is data stream
                    // reference because
                    // contains a stream of data which represents a object
                    Reference reference = new DataStreamReference();
                    // loads GDG generation!! it meeans the real file name of
                    // generation
                    GDGManager.load(ddImpl);

                    LogAppl.getInstance().emit(JBpmMessage.JEMM034I, ddImpl);
                    // serialize data descriptor object into xml string
                    // in this way is easier pass to object across different
                    // classloader, by JNDI.
                    // This xml, by reference, will be used by DataStreamFactory
                    // when
                    // java main class requests a resource by a JNDI call
                    String xml = xstream.toXML(ddImpl);
                    // adds the String into a data stream reference
                    reference.add(new StringRefAddr(StringRefAddrKeys.DATASTREAMS_KEY, xml));
                    // re-bind the object inside the JNDI context
                    ic.rebind(ddImpl.getName(), reference);
                }

            }
            batchSM.setInternalAction(false);
            // executes the java main class defined in JCL
            // setting the boolean to TRUE
            isExecutionStarted = true;
            return item.execute(parms);
        } catch (RemoteException e) {
            throw new JemException(e);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new JemException(e);
        } finally {
            batchSM.setInternalAction(true);
            // checks datasets list
            if (ddList != null && !ddList.isEmpty()) {
                StringBuilder exceptions = new StringBuilder();
                // scans data descriptions
                for (DataDescriptionImpl ddImpl : ddList) {
                    try {
                        // consolidates the GDG situation
                        // changing the root (is a properties file)
                        // only if execution started
                        if (isExecutionStarted) {
                            GDGManager.store(ddImpl);
                        }
                    } catch (IOException e) {
                        // ignore
                        LogAppl.getInstance().ignore(e.getMessage(), e);
                        LogAppl.getInstance().emit(JBpmMessage.JEMM036E, e.getMessage());
                        if (exceptions.length() == 0) {
                            exceptions.append(JBpmMessage.JEMM036E.toMessage().getFormattedMessage(e.getMessage()));
                        } else {
                            exceptions.append(JBpmMessage.JEMM036E.toMessage().getFormattedMessage(e.getMessage()))
                                    .append("\n");
                        }
                    }
                    // unbinds all data sources
                    try {
                        ic.unbind(ddImpl.getName());
                    } catch (NamingException e) {
                        // ignore
                        LogAppl.getInstance().ignore(e.getMessage(), e);
                        LogAppl.getInstance().emit(JBpmMessage.JEMM037E, e.getMessage());
                    }
                }
                // checks if has exception using the stringbuffer
                // used to collect exception string. 
                // Stringbuffer is not empty, throws an exception
                if (exceptions.length() > 0) {
                    LogAppl.getInstance().emit(JBpmMessage.JEMM055E, exceptions.toString());
                }
            }
            for (DataSource source : task.getDataSources()) {
                if (source.getName() != null) {
                    // unbinds all resources
                    try {
                        ic.unbind(source.getName());
                    } catch (NamingException e) {
                        // ignore
                        LogAppl.getInstance().ignore(e.getMessage(), e);
                        LogAppl.getInstance().emit(JBpmMessage.JEMM037E, e.getMessage());
                    }
                }
            }
            batchSM.setInternalAction(false);
        }
    }

    /**
     * Creates a JNDI reference looking up via RMI to JEM node, using the data source specified on JCL and resource.
     * <br>
     * List of data description are necessary for the resources which could be used as streams on datasets.
     * 
     * @param resourcer singleton to get CommonResource object by RMI
     * @param res resource of JEM
     * @param source data source defined in the JCL
     * @param dataDescriptionImplList list of data description defined on the step
     * @return JNDI reference
     * @throws MessageException if any error accours
     */
    private Reference getReference(CommonResourcer resourcer, Resource res, DataSource source,
            List<DataDescriptionImpl> ddList) throws MessageException {
        // creates a JNDI reference
        Reference ref = null;
        try {
            // gets JNDI reference
            ref = resourcer.lookupReference(JobId.VALUE, res.getType());
            // if null, exception
            if (ref == null) {
                throw new MessageException(JBpmMessage.JEMM030E, res.getName(), res.getType());
            }
            // if reference needs data set descriptions, because is possible to use it
            // as data source on data description
            // calls load method
            if (ref instanceof ResourceLoaderReference) {
                ResourceLoaderReference loader = (ResourceLoaderReference) ref;
                loader.loadResource(res, ddList, source.getName());
            }
        } catch (Exception e) {
            throw new MessageException(JBpmMessage.JEMM030E, e, res.getName(), res.getType());
        }
        return ref;
    }

    /**
     * Replaces inside of property value system variables or properties loaded by ANT
     * @param value property value to change
     * @return value changed
     */
    private String replaceProperties(String value, Properties props) {
        String changed = null;
        // if property starts with jem.data
        // I need to ask to DataPaths Container in which data path I can put the file
        if (value.startsWith("${" + ConfigKeys.JEM_DATA_PATH_NAME + "}")) {
            // takes the rest of file name
            String fileName = StringUtils.substringAfter(value, "${" + ConfigKeys.JEM_DATA_PATH_NAME + "}");
            // checks all paths
            try {
                // gets datapaths
                PathsContainer paths = DataPathsContainer.getInstance().getPaths(fileName);
                // is relative!
                // creates a file with dataPath as parent, plus file name  
                File file = new File(paths.getCurrent().getContent(), fileName);
                // the absolute name of the file is property value
                changed = file.getAbsolutePath();
            } catch (InvalidDatasetNameException e) {
                throw new BuildException(e);
            }
        } else {
            // uses substituter utilities to changed all properties
            changed = VariableSubstituter.substitute(value, props);
        }
        return changed;
    }

}