org.jbpm.instance.migration.MigrationUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.jbpm.instance.migration.MigrationUtils.java

Source

/*
 * *##% 
 * jBPM Instance Migrator
 * Copyright (C) null - 2010 JBoss Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 of the
 * License, or (at your option) 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 Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
 * ##%*
 */
package org.jbpm.instance.migration;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.jbpm.JbpmContext;
import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.graph.exe.ProcessInstance;
import org.jbpm.graph.exe.Token;
import org.jbpm.instance.migration.util.JbpmInstanceMigratorLogger;

/**
 * Provides utility methods to the Migrator class.
 * 
 * @see Migrator
 * @author Caleb Powell <caleb.powell@gmail.com> 
 * @author David Harcombe <david.harcombe@intelliware.ca> 
 */
public class MigrationUtils {

    private static final String JAVA_SYNTAX_PREFIX = "java://";
    private static Logger logger = Logger.getLogger(JbpmInstanceMigratorLogger.class);

    /**
     * Convert an xml file into a valid jBPM ProcessDefinition class
     *  
     * @param filename The xml file containing the ProcessDefinition definition
     * @return A ProcessDefinition
     * @throws IOException
     */
    public static ProcessDefinition getProcessDefinition(String filename) throws IOException {
        String processDefinition = FileUtils
                .readFileToString(new File(MigrationUtils.class.getResource(filename).getPath()), "UTF-8");
        return ProcessDefinition.parseXmlString(processDefinition);
    }

    /**
     * Taking a base class name, this method will search the Classpath for all Migrations that begin with that name. The migrations will be returned
     * in an array. For example, given the base Class name 'com.foo.BarMigration', this method will attempt to load classes named 
     * 'com.foo.BarMigration001', 'com.foo.BarMigration002', 'com.foo.BarMigration003', etc. It will stop loading when it catches a {@link ClassNotFoundException}.
     *  
     * Valid Migration classes must implement the {@link Migration} interface (but not be an interface themselves), have a default constructor, and cannot be abstract.
     * 
     * @param baseClassName
     * @return 
     */
    public static Migration[] lookupMigrationsFor(String baseClassName) {
        int revision = 0;
        List migrations = new ArrayList();
        while (true) {
            String migrationName = constructMigrationClassName(baseClassName, ++revision);
            try {
                Class migrationClass = Class.forName(migrationName);
                if (isValidMigration(migrationClass)) {
                    migrations.add(migrationClass.newInstance());
                }
            } catch (ClassNotFoundException e) {
                if (CollectionUtils.isEmpty(migrations)) {
                    logger.info("Could not locate any migrations for the base Class name [" + baseClassName + "].");
                }
                break;
            } catch (InstantiationException e) {
                String errorMessage = "The " + migrationName
                        + " migration could not be instantiated. Please ensure it has a default constructor.";
                logger.error(errorMessage, e);
                throw new InvalidMigrationException(errorMessage, e);
            } catch (IllegalAccessException e) {
                String errorMessage = "The " + migrationName + " migration could not be instantiated.";
                logger.error(errorMessage, e);
                throw new InvalidMigrationException(errorMessage, e);
            }
        }
        return (Migration[]) migrations.toArray(new Migration[migrations.size()]);
    }

    /**
     * The method joins a base Class name and a revision number. It will pad the revision number if necessary.
     * @param baseClassName
     * @param revision
     * @return
     */
    private static String constructMigrationClassName(String baseClassName, int revision) {
        return baseClassName + StringUtils.leftPad(Integer.toString(revision), 3, "0");
    }

    /**
     * 
     * @param migrationClass
     * @return true is this class is an assignable to {@link Migration}, is not an interface, and is not abstract.
     */
    private static boolean isValidMigration(Class migrationClass) {
        return Migration.class.isAssignableFrom(migrationClass) && isConcreteClass(migrationClass);
    }

    /**
     * 
     * @param migrationClass
     * @return true is this class is an assignable to {@link DynamicMigration}, is not an interface, and is not abstract.
     */
    private static boolean isValidDynamicMigration(Class migrationClass) {
        return DynamicMigration.class.isAssignableFrom(migrationClass) && isConcreteClass(migrationClass);
    }

    private static boolean isConcreteClass(Class type) {
        return !type.isInterface() && !Modifier.isAbstract(type.getModifiers());
    }

    /**
     * Used to determine whether a ProcessInstance requires migration.
     * @param processInstance
     * @return true if the processInstance (or any of it's subProcessInstance's) belongs to an outdated processDefinition.
     */
    public static boolean requiresMigration(ProcessInstance processInstance, JbpmContext jbpmContext) {
        ProcessDefinition latestProcessDefinition = MigrationUtils
                .findLatestProcessDefinition(processInstance.getProcessDefinition().getName(), jbpmContext);
        //1. look at the given process definition
        boolean processInstanceRequiresMigration = processInstance.getProcessDefinition()
                .getVersion() != latestProcessDefinition.getVersion();

        //2. search for sub process definitions
        List subProcesses = findSubProcesses(processInstance.getRootToken());
        for (Iterator iter = subProcesses.iterator(); processInstanceRequiresMigration == false
                && iter.hasNext();) {
            ProcessInstance subProcessInstance = (ProcessInstance) iter.next();
            processInstanceRequiresMigration = requiresMigration(subProcessInstance, jbpmContext);
        }
        logger.info("Checking whether process instance[@id='" + processInstance.getId()
                + "'] requires migration? => " + processInstanceRequiresMigration);
        return processInstanceRequiresMigration;
    }

    private static List findSubProcesses(Token token) {
        ArrayList processInstances = new ArrayList();
        ProcessInstance processInstance = token.getSubProcessInstance();
        if (processInstance != null) {
            processInstances.add(processInstance);
            processInstances.addAll(findSubProcesses(processInstance.getRootToken()));
        }

        Map children = token.getChildren();
        if (children != null) {
            for (Iterator iter = children.values().iterator(); iter.hasNext();) {
                Token childToken = (Token) iter.next();
                processInstances.addAll(findSubProcesses(childToken));
            }
        }
        return processInstances;
    }

    /**
     * Used to find a {@link ProcessDefinition} by it's name.
     * 
     * @param processName The name of the process definition.
     * @param jbpmContext
     * @return
     */
    public static ProcessDefinition findLatestProcessDefinition(String processName, JbpmContext jbpmContext) {
        return jbpmContext.getGraphSession().findLatestProcessDefinition(processName);
    }

    /**
     * 
     * @param className
     * @return
     */
    public static DynamicMigration lookupDynamicMigration(String migrationClassName) {
        try {
            Class migrationClass = Class.forName(migrationClassName);
            if (isValidDynamicMigration(migrationClass)) {
                return (DynamicMigration) migrationClass.newInstance();
            } else {
                String errorMessage = "The type '" + migrationClassName
                        + "' is not a valid DynamicMigration. Please ensure your Class  implements '"
                        + DynamicMigration.class.getName()
                        + "', is not abstract and contains a default constructor.";
                logger.error(errorMessage);
                throw new InvalidMigrationException(errorMessage);
            }
        } catch (ClassNotFoundException e) {
            String errorMessage = "Could not locate DynamicMigration Class name [" + migrationClassName + "].";
            logger.error(errorMessage);
            throw new InvalidMigrationException(errorMessage, e);
        } catch (InstantiationException e) {
            String errorMessage = "The " + migrationClassName
                    + " migration could not be instantiated. Please ensure it has a default constructor.";
            logger.error(errorMessage, e);
            throw new InvalidMigrationException(errorMessage, e);
        } catch (IllegalAccessException e) {
            String errorMessage = "The " + migrationClassName + " migration could not be instantiated.";
            logger.error(errorMessage, e);
            throw new InvalidMigrationException(errorMessage, e);
        }
    }

    /**
     * 
     * @param currentNodeName
     * @return true if the <i>currentNodeName</i> parameter is not null and starts with "java://".
     */
    static boolean isDynamicNodeName(String currentNodeName) {
        return currentNodeName != null && currentNodeName.startsWith(JAVA_SYNTAX_PREFIX);
    }

    /**
     * Convenience method to parse the Class name from a dynamic node name. This method expects the 
     * <i>dynamicNodeName</i> parameter to be in the format <i>java://{class name}</i>.
     * 
     * This method delegates to the org.apache.commons.lang.StringUtils.substringAfter() method. When given a 
     * <code>null</code> string input will return <code>null</code>. An empty ("") string input will return the 
     * empty string. A <code>null</code> separator will return the empty string if the input string is not <code>null</code>.
     * @param dynamicNodeName
     * @return
     */
    public static String parseClassNameFromDynamicNode(String dynamicNodeName) {
        return StringUtils.substringAfter(dynamicNodeName, JAVA_SYNTAX_PREFIX);
    }
}