/* * Copyright 2013 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. * 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.jbpm.runtime.manager.impl; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReentrantLock; import org.drools.core.time.TimerService; import org.drools.persistence.api.OrderedTransactionSynchronization; import org.drools.persistence.api.TransactionManager; import org.drools.persistence.api.TransactionManagerFactory; import org.drools.persistence.api.TransactionManagerHelper; import org.drools.persistence.api.TransactionSynchronization; import org.jbpm.process.core.timer.GlobalSchedulerService; import org.jbpm.process.core.timer.TimerServiceRegistry; import org.jbpm.process.core.timer.impl.GlobalTimerService; import org.jbpm.runtime.manager.api.SchedulerProvider; import org.jbpm.runtime.manager.impl.deploy.DeploymentDescriptorManager; import org.jbpm.runtime.manager.impl.error.DefaultExecutionErrorStorage; import org.jbpm.runtime.manager.impl.error.ExecutionErrorManagerImpl; import org.jbpm.services.task.impl.TaskContentRegistry; import org.jbpm.services.task.wih.ExternalTaskEventListener; import org.kie.api.event.process.ProcessEventListener; import org.kie.api.event.rule.AgendaEventListener; import org.kie.api.event.rule.RuleRuntimeEventListener; import org.kie.api.runtime.Environment; import org.kie.api.runtime.EnvironmentName; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.manager.Context; import org.kie.api.runtime.manager.RegisterableItemsFactory; import org.kie.api.runtime.manager.RuntimeEngine; import org.kie.api.runtime.manager.RuntimeEnvironment; import org.kie.api.runtime.process.WorkItemHandler; import org.kie.api.task.TaskLifeCycleEventListener; import org.kie.internal.runtime.conf.DeploymentDescriptor; import org.kie.internal.runtime.error.ExecutionErrorManager; import org.kie.internal.runtime.error.ExecutionErrorStorage; import org.kie.internal.runtime.manager.CacheManager; import org.kie.internal.runtime.manager.Disposable; import org.kie.internal.runtime.manager.DisposeListener; import org.kie.internal.runtime.manager.InternalRegisterableItemsFactory; import org.kie.internal.runtime.manager.InternalRuntimeManager; import org.kie.internal.runtime.manager.RuntimeManagerRegistry; import org.kie.internal.runtime.manager.SecurityManager; import org.kie.internal.runtime.manager.SessionNotFoundException; import org.kie.internal.runtime.manager.context.ProcessInstanceIdContext; import org.kie.internal.task.api.EventService; import org.kie.internal.task.api.InternalTaskService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Common implementation that all <code>RuntimeManager</code> implementations should inherit from. * Provides the following capabilities: * <ul> * <li>keeps track of all active managers by their identifier and prevents multiple managers from having the same id</li> * <li>provides a common close operation</li> * <li>injects the RuntimeManager into the ksession's environment for further reference</li> * <li>registers dispose callbacks (via transaction synchronization) * to dispose of the runtime engine automatically on transaction completion</li> * <li>registers all defined items (work item handlers, event listeners)</li> * </ul> * Additionally, this provides a abstract <code>init</code> method that will be called on RuntimeManager instantiation. */ public abstract class AbstractRuntimeManager implements InternalRuntimeManager { private static final Logger logger = LoggerFactory.getLogger(AbstractRuntimeManager.class); protected RuntimeManagerRegistry registry = RuntimeManagerRegistry.get(); protected RuntimeEnvironment environment; protected DeploymentDescriptor deploymentDescriptor; protected KieContainer kieContainer; protected CacheManager cacheManager = new CacheManagerImpl(); protected boolean engineInitEager = Boolean.parseBoolean(System.getProperty("org.jbpm.rm.engine.eager", "false")); protected String identifier; protected boolean closed = false; protected SecurityManager securityManager = null; protected ExecutionErrorManager executionErrorManager; protected ConcurrentMap<Long, ReentrantLock> engineLocks = new ConcurrentHashMap<Long, ReentrantLock>(); public AbstractRuntimeManager(RuntimeEnvironment environment, String identifier) { this.environment = environment; this.identifier = identifier; if (registry.isRegistered(identifier)) { throw new IllegalStateException("RuntimeManager with id " + identifier + " is already active"); } internalSetDeploymentDescriptor(); internalSetKieContainer(); ((InternalRegisterableItemsFactory)environment.getRegisterableItemsFactory()).setRuntimeManager(this); String eagerInit = (String)((SimpleRuntimeEnvironment)environment).getEnvironmentTemplate().get("RuntimeEngineEagerInit"); if (eagerInit != null) { engineInitEager = Boolean.parseBoolean(eagerInit); } ExecutionErrorStorage storage = (ExecutionErrorStorage) ((SimpleRuntimeEnvironment)environment).getEnvironmentTemplate().get("ExecutionErrorStorage"); if (storage == null) { storage = new DefaultExecutionErrorStorage(environment.getEnvironment()); } this.executionErrorManager = new ExecutionErrorManagerImpl(storage); ((SimpleRuntimeEnvironment)environment).getEnvironmentTemplate().set(EnvironmentName.EXEC_ERROR_MANAGER, executionErrorManager); } private void internalSetDeploymentDescriptor() { this.deploymentDescriptor = (DeploymentDescriptor) ((SimpleRuntimeEnvironment)environment).getEnvironmentTemplate().get("KieDeploymentDescriptor"); if (this.deploymentDescriptor == null) { this.deploymentDescriptor = new DeploymentDescriptorManager().getDefaultDescriptor(); } } private void internalSetKieContainer() { this.kieContainer = (KieContainer) ((SimpleRuntimeEnvironment)environment).getEnvironmentTemplate().get("KieContainer"); } public abstract void init(); protected void registerItems(RuntimeEngine runtime) { RegisterableItemsFactory factory = environment.getRegisterableItemsFactory(); // process handlers Map<String, WorkItemHandler> handlers = factory.getWorkItemHandlers(runtime); for (Entry<String, WorkItemHandler> entry : handlers.entrySet()) { runtime.getKieSession().getWorkItemManager().registerWorkItemHandler(entry.getKey(), entry.getValue()); } // register globals Map<String, Object> globals = factory.getGlobals(runtime); for (Entry<String, Object> entry : globals.entrySet()) { runtime.getKieSession().setGlobal(entry.getKey(), entry.getValue()); } // process listeners List<ProcessEventListener> processListeners = factory.getProcessEventListeners(runtime); for (ProcessEventListener listener : processListeners) { runtime.getKieSession().addEventListener(listener); } // agenda listeners List<AgendaEventListener> agendaListeners = factory.getAgendaEventListeners(runtime); for (AgendaEventListener listener : agendaListeners) { runtime.getKieSession().addEventListener(listener); } // working memory listeners List<RuleRuntimeEventListener> wmListeners = factory.getRuleRuntimeEventListeners(runtime); for (RuleRuntimeEventListener listener : wmListeners) { runtime.getKieSession().addEventListener(listener); } } protected void registerDisposeCallback(RuntimeEngine runtime, TransactionSynchronization sync) { if (hasEnvironmentEntry("IS_JTA_TRANSACTION", false)) { return; } // register it if there is an active transaction as we assume then to be running in a managed environment e.g CMT TransactionManager tm = getTransactionManager(runtime.getKieSession().getEnvironment()); if (tm.getStatus() != TransactionManager.STATUS_NO_TRANSACTION && tm.getStatus() != TransactionManager.STATUS_ROLLEDBACK && tm.getStatus() != TransactionManager.STATUS_COMMITTED) { TransactionManagerHelper.registerTransactionSyncInContainer(tm, (OrderedTransactionSynchronization) sync); } } protected boolean canDispose(RuntimeEngine runtime) { // avoid duplicated dispose if (((RuntimeEngineImpl)runtime).isDisposed()) { return false; } // if this method was called as part of afterCompletion or is no JTA at all, allow to dispose if (((RuntimeEngineImpl)runtime).isAfterCompletion() || hasEnvironmentEntry("IS_JTA_TRANSACTION", false)) { return true; } try { // check tx status to disallow dispose when within active transaction TransactionManager tm = getTransactionManager(runtime.getKieSession().getEnvironment()); if (tm.getStatus() != TransactionManager.STATUS_NO_TRANSACTION && tm.getStatus() != TransactionManager.STATUS_ROLLEDBACK && tm.getStatus() != TransactionManager.STATUS_COMMITTED) { return false; } } catch (SessionNotFoundException e) { // ignore it as it might be thrown for per process instance } return true; } protected void attachManager(RuntimeEngine runtime) { runtime.getKieSession().getEnvironment().set(EnvironmentName.RUNTIME_MANAGER, this); runtime.getKieSession().getEnvironment().set(EnvironmentName.DEPLOYMENT_ID, this.getIdentifier()); } @Override public boolean isClosed() { return this.closed; } @Override public void close() { close(false); } public void close(boolean removeJobs) { cacheManager.dispose(); environment.close(); registry.remove(identifier); TimerService timerService = TimerServiceRegistry.getInstance().remove(getIdentifier() + TimerServiceRegistry.TIMER_SERVICE_SUFFIX); if (timerService != null) { if (removeJobs && timerService instanceof GlobalTimerService) { ((GlobalTimerService) timerService).destroy(); } timerService.shutdown(); GlobalSchedulerService schedulerService = ((SchedulerProvider) environment).getSchedulerService(); if (schedulerService != null) { schedulerService.shutdown(); } } this.closed = true; } public org.kie.internal.runtime.manager.RuntimeEnvironment getEnvironment() { return (org.kie.internal.runtime.manager.RuntimeEnvironment)environment; } public void setEnvironment(RuntimeEnvironment environment) { this.environment = environment; } public String getIdentifier() { return identifier; } public void setIdentifier(String identifier) { this.identifier = identifier; } @SuppressWarnings({ "unchecked", "rawtypes" }) protected void configureRuntimeOnTaskService(InternalTaskService internalTaskService, RuntimeEngine engine) { if (internalTaskService != null) { ExternalTaskEventListener listener = new ExternalTaskEventListener(); if (internalTaskService instanceof EventService) { ((EventService)internalTaskService).registerTaskEventListener(listener); } // register task listeners if any RegisterableItemsFactory factory = environment.getRegisterableItemsFactory(); for (TaskLifeCycleEventListener taskListener : factory.getTaskListeners()) { ((EventService<TaskLifeCycleEventListener>)internalTaskService).registerTaskEventListener(taskListener); } if (engine != null && engine instanceof Disposable) { ((Disposable)engine).addDisposeListener(new DisposeListener() { @Override public void onDispose(RuntimeEngine runtime) { if (runtime.getTaskService() instanceof EventService) { ((EventService)runtime.getTaskService()).clearTaskEventListeners();; } } }); } } } protected void removeRuntimeFromTaskService() { TaskContentRegistry.get().removeMarshallerContext(getIdentifier()); } /** * Soft dispose means it will be invoked as sort of preparation step before actual dispose. * Mainly used with transaction synchronization to be invoked as part of beforeCompletion * to clean up any thread state - like thread local settings as afterCompletion can be invoked from another thread */ public void softDispose(RuntimeEngine runtimeEngine) { } protected boolean canDestroy(RuntimeEngine runtime) { if (hasEnvironmentEntry("IS_JTA_TRANSACTION", false) || ((RuntimeEngineImpl) runtime).isAfterCompletion()) { return false; } TransactionManager tm = getTransactionManager(runtime.getKieSession().getEnvironment()); if (tm.getStatus() == TransactionManager.STATUS_NO_TRANSACTION || tm.getStatus() == TransactionManager.STATUS_ACTIVE) { return true; } return false; } protected boolean hasEnvironmentEntry(String name, Object value) { Object envEntry = environment.getEnvironment().get(name); if (value == null) { return envEntry == null; } return value.equals(envEntry); } protected TransactionManager getTransactionManager(Environment env) { if (env == null) { env = environment.getEnvironment(); } Object txm = env.get(EnvironmentName.TRANSACTION_MANAGER); if (txm != null && txm instanceof TransactionManager) { return (TransactionManager) txm; } return TransactionManagerFactory.get().newTransactionManager(env); } protected TransactionManager getTransactionManagerInternal(Environment env) { try { return getTransactionManager(env); } catch (Exception e) { // return no op transaction manager as none were found so let the ksession manage the tx instead return new TransactionManager() { @Override public void rollback(boolean transactionOwner) { } @Override public void registerTransactionSynchronization(TransactionSynchronization ts) { } @Override public void putResource(Object key, Object resource) { } @Override public int getStatus() { return STATUS_NO_TRANSACTION; } @Override public Object getResource(Object key) { return null; } @Override public void commit(boolean transactionOwner) { } @Override public boolean begin() { return false; } }; } } @Override public DeploymentDescriptor getDeploymentDescriptor() { return deploymentDescriptor; } @Override public void setDeploymentDescriptor(DeploymentDescriptor deploymentDescriptor) { this.deploymentDescriptor = deploymentDescriptor; } @Override public void setSecurityManager(SecurityManager securityManager) { if (this.securityManager != null) { throw new IllegalStateException("Security Manager for " + this.identifier + " manager is already set"); } this.securityManager = securityManager; } protected void checkPermission() { if (this.securityManager != null) { this.securityManager.checkPermission(); } } @Override public void setCacheManager(CacheManager cacheManager) { if (cacheManager != null) { this.cacheManager = cacheManager; } } @Override public CacheManager getCacheManager() { return cacheManager; } @Override public KieContainer getKieContainer() { return kieContainer; } @Override public void setKieContainer(KieContainer kieContainer) { this.kieContainer = kieContainer; } /* * locking support for same context - runtime engine that deals with exact same process instance context */ protected boolean isUseLocking() { return false; } protected void createLockOnGetEngine(Context<?> context, RuntimeEngine runtime) { if (!isUseLocking()) { logger.debug("Locking on runtime manager disabled"); return; } if (context instanceof ProcessInstanceIdContext) { Long piId = ((ProcessInstanceIdContext) context).getContextId(); createLockOnGetEngine(piId, runtime); } } protected void createLockOnGetEngine(Long id, RuntimeEngine runtime) { if (!isUseLocking()) { logger.debug("Locking on runtime manager disabled"); return; } if (id != null) { ReentrantLock newLock = new ReentrantLock(); ReentrantLock lock = engineLocks.putIfAbsent(id, newLock); if (lock == null) { lock = newLock; logger.debug("New lock created as it did not exist before"); } else { logger.debug("Lock exists with {} waiting threads", lock.getQueueLength()); } logger.debug("Trying to get a lock {} for {} by {}", lock, id, runtime); lock.lock(); logger.debug("Lock {} taken for {} by {} for waiting threads by {}", lock, id, runtime, lock.hasQueuedThreads()); } } protected void createLockOnNewProcessInstance(Long id, RuntimeEngine runtime) { if (!isUseLocking()) { logger.debug("Locking on runtime manager disabled"); return; } ReentrantLock newLock = new ReentrantLock(); ReentrantLock lock = engineLocks.putIfAbsent(id, newLock); if (lock == null) { lock = newLock; } lock.lock(); logger.debug("[on new process instance] Lock {} created and stored in list by {}", lock, runtime); } protected void releaseAndCleanLock(RuntimeEngine runtime) { if (!isUseLocking()) { logger.debug("Locking on runtime manager disabled"); return; } if (((RuntimeEngineImpl)runtime).getContext() instanceof ProcessInstanceIdContext) { Long piId = ((ProcessInstanceIdContext) ((RuntimeEngineImpl)runtime).getContext()).getContextId(); if (piId != null) { releaseAndCleanLock(piId, runtime); } } } protected void releaseAndCleanLock(Long id, RuntimeEngine runtime) { if (id != null) { ReentrantLock lock = engineLocks.get(id); if (lock != null) { if (!lock.hasQueuedThreads()) { logger.debug("Removing lock {} from list as non is waiting for it by {}", lock, runtime); engineLocks.remove(id); } if (lock.isHeldByCurrentThread()) { lock.unlock(); logger.debug("{} unlocked by {}", lock, runtime); } } } } protected boolean isActive() { if (hasEnvironmentEntry("Active", false)) { return false; } return true; } @Override public void activate() { ((SimpleRuntimeEnvironment) environment).addToEnvironment("Active", true); } @Override public void deactivate() { ((SimpleRuntimeEnvironment) environment).addToEnvironment("Active", false); } public ExecutionErrorManager getExecutionErrorManager() { return executionErrorManager; } }