org.bpmscript.process.memory.MemoryBpmScriptManager.java Source code

Java tutorial

Introduction

Here is the source code for org.bpmscript.process.memory.MemoryBpmScriptManager.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.bpmscript.process.memory;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

import org.apache.commons.collections.MultiHashMap;
import org.bpmscript.BpmScriptException;
import org.bpmscript.IExecutorResult;
import org.bpmscript.ProcessState;
import org.bpmscript.journal.IContinuationJournal;
import org.bpmscript.journal.IContinuationJournalEntry;
import org.bpmscript.journal.memory.MemoryContinuationJournalEntry;
import org.bpmscript.paging.IOrderBy;
import org.bpmscript.paging.IPagedResult;
import org.bpmscript.paging.IQuery;
import org.bpmscript.paging.PagedResult;
import org.bpmscript.paging.Query;
import org.bpmscript.process.IInstance;
import org.bpmscript.process.IInstanceCallback;
import org.bpmscript.process.IInstanceManager;
import org.bpmscript.process.IInstanceStatusManager;
import org.bpmscript.process.lock.LockingInstanceManager;
import org.bpmscript.util.ReflectionComparator;

/**
 * 
 */
@SuppressWarnings("unchecked")
public class MemoryBpmScriptManager implements IInstanceManager, IContinuationJournal, IInstanceStatusManager {

    private final transient org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
            .getLog(getClass());

    private Map<String, MemoryInstance> memoryInstances = Collections
            .synchronizedMap(new LinkedHashMap<String, MemoryInstance>(16, 0.75F, true));
    private Map versionIndex = Collections.synchronizedMap(new MultiHashMap());
    private Map pidIndex = Collections.synchronizedMap(new MultiHashMap());
    private Map branchIndex = Collections.synchronizedMap(new MultiHashMap());
    private boolean clearOnComplete = true;
    private int maxEntries = 1000;
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static Map<String, AtomicInteger> locks = new ConcurrentHashMap<String, AtomicInteger>();

    /**
     * @see org.bpmscript.journal.IContinuationJournal#createBranch(java.lang.String)
     */
    public String createBranch(String version) {
        MemoryContinuationJournalEntry currentEntry = null;
        ReadLock readLock = readWriteLock.readLock();
        try {
            readLock.lock();
            currentEntry = (MemoryContinuationJournalEntry) ((Collection) versionIndex.get(version)).iterator()
                    .next();
        } finally {
            readLock.unlock();
        }
        String branch = UUID.randomUUID().toString();
        MemoryContinuationJournalEntry newEntry = new MemoryContinuationJournalEntry();
        newEntry.setId(UUID.randomUUID().toString());
        newEntry.setContinuation(currentEntry.getContinuation());
        newEntry.setInstanceId(currentEntry.getInstanceId());
        newEntry.setProcessState(currentEntry.getProcessState());
        newEntry.setVersion(version);
        newEntry.setBranch(branch);
        newEntry.setLastModified(new Timestamp(System.currentTimeMillis()));
        storeEntry(newEntry);
        return branch;
    }

    /**
     * Stores a continuation entry. Used wherever a continuation entry is created so that all the
     * indexes are populated.
     * 
     * @param entry the entry to store
     */
    public void storeEntry(MemoryContinuationJournalEntry entry) {
        WriteLock writeLock = readWriteLock.writeLock();
        try {
            writeLock.lock();
            versionIndex.put(entry.getVersion(), entry);
            pidIndex.put(entry.getInstanceId(), entry);
            branchIndex.put(entry.getBranch(), entry);
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * @see org.bpmscript.journal.IContinuationJournal#createMainBranch(java.lang.String)
     */
    public String createMainBranch(String pid) {
        String branch = UUID.randomUUID().toString();
        return branch;
    }

    /**
     * @see org.bpmscript.journal.IContinuationJournal#createVersion(java.lang.String,
     *      java.lang.String)
     */
    public String createVersion(String pid, String branch) {
        return UUID.randomUUID().toString();
    }

    /**
     * @see org.bpmscript.journal.IContinuationJournal#getBranchesForPid(java.lang.String)
     */
    public Collection<String> getBranchesForPid(String pid) {
        ReadLock readLock = readWriteLock.readLock();
        try {
            readLock.lock();
            Collection<MemoryContinuationJournalEntry> entries = ((Collection<MemoryContinuationJournalEntry>) pidIndex
                    .get(pid));
            HashSet<String> result = new HashSet<String>();
            if (entries != null) {
                for (MemoryContinuationJournalEntry entry : entries) {
                    result.add(entry.getBranch());
                }
            }
            return result;
        } finally {
            readLock.unlock();
        }
    }

    /**
     * @see org.bpmscript.journal.IContinuationJournal#getContinuationLatest(java.lang.String)
     */
    public byte[] getContinuationLatest(String branch) {
        ReadLock readLock = readWriteLock.readLock();
        try {
            readLock.lock();
            Collection entries = ((Collection) branchIndex.get(branch));
            if (entries != null) {
                int size = entries.size();
                MemoryContinuationJournalEntry entry = (MemoryContinuationJournalEntry) entries.toArray()[size - 1];
                return entry.getContinuation();
            } else {
                return null;
            }
        } finally {
            readLock.unlock();
        }
    }

    /**
     * @see org.bpmscript.journal.IContinuationJournal#getLiveResults(java.lang.String)
     */
    public Collection<IContinuationJournalEntry> getLiveResults(String pid) {
        ReadLock readLock = readWriteLock.readLock();
        try {
            readLock.lock();
            Collection<IContinuationJournalEntry> entries = (Collection<IContinuationJournalEntry>) pidIndex
                    .get(pid);
            ArrayList<IContinuationJournalEntry> result = new ArrayList<IContinuationJournalEntry>();
            for (IContinuationJournalEntry entry : entries) {
                switch (entry.getProcessState()) {
                case CREATED:
                case RUNNING:
                case PAUSED:
                    result.add(entry);
                    break;
                case COMPLETED:
                case FAILED:
                case KILLED:
                    break;
                }
            }
            return result;
        } finally {
            readLock.unlock();
        }
    }

    /**
     * @see org.bpmscript.journal.IContinuationJournal#getProcessStateLatest(java.lang.String)
     */
    public ProcessState getProcessStateLatest(String branch) {
        ReadLock readLock = readWriteLock.readLock();
        try {
            readLock.lock();
            Collection entries = ((Collection) branchIndex.get(branch));
            if (entries == null)
                return null;
            int size = entries.size();
            if (size == 0)
                return null;
            MemoryContinuationJournalEntry entry = (MemoryContinuationJournalEntry) entries.toArray()[size - 1];
            return entry.getProcessState();
        } finally {
            readLock.unlock();
        }
    }

    /**
     * @see org.bpmscript.journal.IContinuationJournal#isDirty(java.lang.String)
     */
    public String getVersionLatest(String branch) {
        ReadLock readLock = readWriteLock.readLock();
        try {
            readLock.lock();
            Collection entries = ((Collection) branchIndex.get(branch));
            int size = entries.size();
            MemoryContinuationJournalEntry entry = (MemoryContinuationJournalEntry) entries.toArray()[size - 1];
            return entry.getVersion();
        } finally {
            readLock.unlock();
        }
    }

    /**
     * @see org.bpmscript.journal.IContinuationJournal#storeResult(byte[],
     *      org.bpmscript.IExecutorResult)
     */
    public void storeResult(byte[] continuation, IExecutorResult result) {
        if (clearOnComplete && result.getProcessState() != ProcessState.PAUSED) {
            cleanUpEntries(result.getPid());
        } else {
            MemoryContinuationJournalEntry entry = new MemoryContinuationJournalEntry();
            entry.setId(UUID.randomUUID().toString());
            entry.setBranch(result.getBranch());
            entry.setContinuation(continuation);
            entry.setInstanceId(result.getPid());
            entry.setLastModified(new Timestamp(System.currentTimeMillis()));
            entry.setProcessState(result.getProcessState());
            entry.setVersion(result.getVersion());
            storeEntry(entry);
        }
    }

    /**
     * This method cleans up any entries related to a branch
     * 
     * @param result
     */
    public void cleanUpEntries(String pid) {
        WriteLock writeLock = readWriteLock.writeLock();
        try {
            writeLock.lock();
            Collection pidEntries = (Collection) pidIndex.get(pid);
            if (pidEntries != null) {
                for (Iterator iterator = pidEntries.iterator(); iterator.hasNext();) {
                    MemoryContinuationJournalEntry entry = (MemoryContinuationJournalEntry) iterator.next();
                    versionIndex.remove(entry.getVersion());
                    branchIndex.remove(entry.getBranch());
                }
                pidIndex.remove(pid);
                memoryInstances.remove(pid);
            }
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * @see org.bpmscript.journal.IContinuationJournal#getResults(java.lang.String)
     */
    public Collection<IContinuationJournalEntry> getEntriesForPid(String pid) {
        ReadLock readLock = readWriteLock.readLock();
        try {
            readLock.lock();
            return (Collection<IContinuationJournalEntry>) pidIndex.get(pid);
        } finally {
            readLock.unlock();
        }
    }

    /**
     * Create a new in memory instance
     */
    public String createInstance(String parentVersion, String definitionId, String definitionName,
            String definitionType, String operation) throws BpmScriptException {

        synchronized (memoryInstances) {
            int memoryInstancesSize = memoryInstances.size();
            if (memoryInstancesSize > maxEntries && maxEntries > 0) {
                ArrayList<String> pidsToRemove = new ArrayList<String>();
                Iterator<String> iterator = memoryInstances.keySet().iterator();
                do {
                    pidsToRemove.add(iterator.next());
                } while (memoryInstancesSize-- > maxEntries);
                for (String pid : pidsToRemove) {
                    log.debug("removing pid " + pid + " as maxEntries has been exceeded");
                    cleanUpEntries(pid);
                }
            }
        }

        String id = UUID.randomUUID().toString();
        MemoryInstance memoryInstance = new MemoryInstance();
        memoryInstance.setId(id);
        memoryInstance.setOperation(operation);
        memoryInstance.setParentVersion(parentVersion);
        memoryInstance.setDefinitionId(definitionId);
        memoryInstance.setDefinitionName(definitionName);
        memoryInstance.setDefinitionType(definitionType);
        memoryInstances.put(id, memoryInstance);
        return id;
    }

    /**
     * Call the callback with the instance reference by the pid. Should be used in
     * conjunction with a locking mechanism, e.g. LockingInstanceManager
     * 
     * @see LockingInstanceManager
     */
    public IExecutorResult doWithInstance(String processInstanceId, IInstanceCallback callback) throws Exception {
        AtomicInteger lock = null;
        synchronized (locks) {
            lock = locks.get(processInstanceId);
            if (lock == null) {
                lock = new AtomicInteger(1);
                locks.put(processInstanceId, lock);
            } else {
                lock.set(lock.get() + 1);
            }
        }
        IExecutorResult result = null;
        synchronized (lock) {
            try {
                log.debug("locking " + processInstanceId + " " + lock);
                MemoryInstance processInstance = memoryInstances.get(processInstanceId);
                result = callback.execute(processInstance);
                if (result.getProcessState() != ProcessState.IGNORED) {
                    processInstance.setLastModified(new Timestamp(System.currentTimeMillis()));
                }
            } finally {
                log.debug("unlocking " + processInstanceId);
                lock.set(lock.get() - 1);
                if (lock.get() == 0) {
                    locks.remove(processInstanceId);
                }
            }
        }
        return result;

    }

    /**
     * Get all instances associated with a definition
     */
    public PagedResult<IInstance> getInstancesForDefinition(IQuery query, String definitionId)
            throws BpmScriptException {
        List<IInstance> results = new ArrayList<IInstance>();
        for (Map.Entry<String, MemoryInstance> entry : memoryInstances.entrySet()) {
            if (definitionId.equals(entry.getValue().getDefinitionId())) {
                results.add(entry.getValue());
            }
        }
        int totalResults = results.size();
        sortResults(results, query.getOrderBys());
        if (query.getMaxResults() > 0) {
            int maxToGet = query.getFirstResult() + query.getMaxResults() + 1;
            results = results.subList(query.getFirstResult(),
                    results.size() > maxToGet ? maxToGet : results.size());
        }
        return new PagedResult<IInstance>(results, query.getMaxResults(), totalResults);
    }

    /**
     * Get child instances
     */
    public List<IInstance> getChildInstances(String parentVersion) throws BpmScriptException {
        List<IInstance> result = new ArrayList<IInstance>();
        for (Map.Entry<String, MemoryInstance> entry : memoryInstances.entrySet()) {
            if (parentVersion.equals(entry.getValue().getParentVersion())) {
                result.add(entry.getValue());
            }
        }
        return result;
    }

    /**
     * Get instance for a pid
     */
    public IInstance getInstance(String pid) throws BpmScriptException {
        return memoryInstances.get(pid);
    }

    /**
     * Get all instances limited by a query, in a paged form
     */
    public IPagedResult<IInstance> getInstances(IQuery query) throws BpmScriptException {
        List<IInstance> results = new ArrayList<IInstance>(memoryInstances.values());

        sortResults(results, query.getOrderBys());
        List<IInstance> trimmedResults = null;
        int lastResult = query.getFirstResult() + query.getMaxResults();

        if (query.getFirstResult() >= 0 && query.getMaxResults() >= 0) {
            trimmedResults = new ArrayList<IInstance>(results.subList(query.getFirstResult(),
                    lastResult < results.size() ? lastResult : results.size()));
        } else {
            trimmedResults = results;
        }

        return new PagedResult<IInstance>(trimmedResults, lastResult > 0 && lastResult < results.size(),
                results.size());
    }

    /**
     * @param results the results to sort
     * @param orderBys the properties to sort by
     * @throws BpmScriptException if something goes wrong (e.g. one of the properties is not valid)
     */
    private void sortResults(List<IInstance> results, final List<IOrderBy> orderBys) throws BpmScriptException {
        if (orderBys != null && orderBys.size() > 0) {
            try {
                Collections.sort(results, new ReflectionComparator(orderBys));
            } catch (RuntimeException e) {
                Throwable cause = e.getCause();
                if (cause != null && cause instanceof BpmScriptException) {
                    throw (BpmScriptException) cause;
                } else {
                    throw e;
                }
            }
        }
    }

    /**
     * @see org.bpmscript.process.IInstanceStatusManager#isLive(java.lang.String)
     */
    public Collection<String> getLiveInstances(String definitionId) throws BpmScriptException {
        final HashSet<String> result = new HashSet<String>();
        IPagedResult<IInstance> instances = this.getInstancesForDefinition(Query.ALL, definitionId);
        for (final IInstance instance : instances.getResults()) {
            try {
                Collection<String> branches = this.getBranchesForPid(instance.getId());
                for (String branch : branches) {
                    ProcessState state = this.getProcessStateLatest(branch);
                    if (state == ProcessState.CREATED || state == ProcessState.RUNNING
                            || state == ProcessState.PAUSED) {
                        result.add(instance.getId());
                    }
                }
            } catch (Throwable e) {
                throw new BpmScriptException(e);
            }
        }
        return result;
    }

}