/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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.jbpm.persistence; import java.util.ArrayList; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.LockModeType; import javax.persistence.NoResultException; import javax.persistence.NonUniqueResultException; import javax.persistence.Query; import org.drools.persistence.api.TransactionManager; import org.drools.persistence.api.TransactionManagerHelper; import org.drools.persistence.jpa.JpaPersistenceContext; import org.jbpm.persistence.api.PersistentCorrelationKey; import org.jbpm.persistence.api.PersistentProcessInstance; import org.jbpm.persistence.api.ProcessPersistenceContext; import org.jbpm.persistence.correlation.CorrelationKeyInfo; import org.jbpm.persistence.processinstance.JPASignalManager; import org.jbpm.persistence.processinstance.ProcessInstanceInfo; import org.jbpm.process.instance.ProcessInstance; import org.jbpm.process.instance.ProcessInstanceManager; import org.kie.internal.process.CorrelationKey; import org.kie.internal.process.CorrelationProperty; public class JpaProcessPersistenceContext extends JpaPersistenceContext implements ProcessPersistenceContext { public JpaProcessPersistenceContext(EntityManager em, TransactionManager txm) { super( em, txm ); } public JpaProcessPersistenceContext(EntityManager em, boolean useJTA, boolean locking, TransactionManager txm) { super( em, useJTA, locking, txm); } public PersistentProcessInstance persist(PersistentProcessInstance processInstanceInfo) { EntityManager em = getEntityManager(); em.persist(processInstanceInfo); TransactionManagerHelper.addToUpdatableSet(txm, processInstanceInfo); if( this.pessimisticLocking ) { em.flush(); return em.find(ProcessInstanceInfo.class, processInstanceInfo.getId(), LockModeType.PESSIMISTIC_FORCE_INCREMENT ); } return processInstanceInfo; } public PersistentProcessInstance findProcessInstanceInfo(Long processId) { EntityManager em = getEntityManager(); if( this.pessimisticLocking ) { return em.find( ProcessInstanceInfo.class, processId, LockModeType.PESSIMISTIC_FORCE_INCREMENT ); } return em.find( ProcessInstanceInfo.class, processId ); } public void remove(PersistentProcessInstance processInstanceInfo) { getEntityManager().remove( processInstanceInfo ); TransactionManagerHelper.removeFromUpdatableSet(txm, processInstanceInfo); List<CorrelationKeyInfo> correlations = getEntityManager().createNamedQuery("GetCorrelationKeysByProcessInstanceId") .setParameter("pId", processInstanceInfo.getId()).getResultList(); if (correlations != null) { for (CorrelationKeyInfo key : correlations) { getEntityManager().remove(key); } } } /** * This method is used by the {@link JPASignalManager} in order to load {@link ProcessInstance} instances * into the {@link ProcessInstanceManager} cache so that they can then be signalled. * </p> * Unfortunately, with regards to locking, the method is not always called during a transaction, which means * that including logic to lock the query will cause exceptions and is not feasible. * </p> * Because the {@link org.drools.core.command.SingleSessionCommandService} design is based around a synchronized execute(...) method, * it's not possible for one thread to create a process instance while another thread simultaneously tries to * signal it. That means that a * <a href="http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Phantom_reads">phantom read</a> * race condition, that might be caused by a lack of pessimistic locking on this query, isn't possible. * </p> * Of course, if you're using multiple ksessions to simultaneoulsy interact with the same process instance, * all bets are off. This however is true for almost everything involving process instances, so that it's not * worth discussing. * </p> */ public List<Long> getProcessInstancesWaitingForEvent(String type) { EntityManager entityManager = getEntityManager(); if (entityManager != null) { Query processInstancesForEvent = getEntityManager().createNamedQuery( "ProcessInstancesWaitingForEvent" ); processInstancesForEvent.setParameter( "type", type ); return (List<Long>) processInstancesForEvent.getResultList(); } else { // entity manager can be null when fireActivationCreated is // called on session unmarshalling return new ArrayList<Long>(); } } public PersistentCorrelationKey persist(PersistentCorrelationKey correlationKeyInfo) { Long processInstanceId = getProcessInstanceByCorrelationKey(correlationKeyInfo); if (processInstanceId != null) { throw new RuntimeException(correlationKeyInfo + " already exists"); } EntityManager em = getEntityManager(); em.persist( correlationKeyInfo ); if( this.pessimisticLocking) { em.flush(); return em.find(CorrelationKeyInfo.class, correlationKeyInfo.getId(), LockModeType.PESSIMISTIC_FORCE_INCREMENT); } return correlationKeyInfo; } /** * With regards to locking, the method is not always called during a transaction, which means * that including logic to lock the query will cause exceptions and is not feasible. * </p> * However, this is not an issue: see the {@link #getProcessInstancesWaitingForEvent(String)} documentation * for more information. The same logic applies to this method. * </p> */ public Long getProcessInstanceByCorrelationKey(CorrelationKey correlationKey) { Query processInstancesForEvent = getEntityManager().createNamedQuery( "GetProcessInstanceIdByCorrelation" ); processInstancesForEvent.setParameter( "ckey", correlationKey.toExternalForm()); try { return (Long) processInstancesForEvent.getSingleResult(); } catch (NonUniqueResultException e) { return null; } catch (NoResultException e) { return null; } } }