/* * 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.HashMap; import java.util.Map; import org.jbpm.runtime.manager.impl.error.ExecutionErrorManagerImpl; import org.jbpm.runtime.manager.impl.factory.LocalTaskServiceFactory; import org.jbpm.runtime.manager.impl.tx.DestroySessionTransactionSynchronization; import org.jbpm.runtime.manager.impl.tx.DisposeSessionTransactionSynchronization; import org.jbpm.services.task.impl.TaskContentRegistry; import org.kie.api.runtime.KieSession; import org.kie.api.runtime.manager.Context; import org.kie.api.runtime.manager.RuntimeEngine; import org.kie.api.runtime.manager.RuntimeEnvironment; import org.kie.api.task.TaskService; import org.kie.internal.runtime.manager.Disposable; import org.kie.internal.runtime.manager.InternalRuntimeManager; import org.kie.internal.runtime.manager.SessionFactory; import org.kie.internal.runtime.manager.TaskServiceFactory; import org.kie.internal.runtime.manager.context.EmptyContext; import org.kie.internal.task.api.ContentMarshallerContext; import org.kie.internal.task.api.InternalTaskService; /** * A RuntimeManager implementation that is backed by the "Per Request" strategy. This means that for every call to * <code>getRuntimeEngine</code>, a new instance will be delivered with brand new KieSession and TaskService. * The only exception to this is when this is invoked within the same transaction from different places. In that case, * the manager caches the currently active instance in a ThreadLocal instane to avoid concurrent modifications or "loss" of data. * Disposing of the runtime engine manager will ensure that it is destroyed as well, so that it will get removed from * the database to avoid outdated data. * <br/> * This implementation does not require any special <code>Context</code> to proceed. * */ public class PerRequestRuntimeManager extends AbstractRuntimeManager { private SessionFactory factory; private TaskServiceFactory taskServiceFactory; private static ThreadLocal<Map<String, RuntimeEngine>> local = new ThreadLocal<Map<String, RuntimeEngine>>() { @Override protected Map<String, RuntimeEngine> initialValue() { return new HashMap<String, RuntimeEngine>(); } }; public PerRequestRuntimeManager(RuntimeEnvironment environment, SessionFactory factory, TaskServiceFactory taskServiceFactory, String identifier) { super(environment, identifier); this.factory = factory; this.taskServiceFactory = taskServiceFactory; this.registry.register(this); } @Override public RuntimeEngine getRuntimeEngine(Context<?> context) { if (isClosed()) { throw new IllegalStateException("Runtime manager " + identifier + " is already closed"); } checkPermission(); RuntimeEngine runtime = null; if (local.get().get(identifier) != null) { RuntimeEngine engine = local.get().get(identifier); // check if engine is not already disposed as afterCompletion might be issued from another thread if (engine != null && ((RuntimeEngineImpl) engine).isDisposed()) { return null; } return engine; } if (engineInitEager) { InternalTaskService internalTaskService = (InternalTaskService) taskServiceFactory.newTaskService(); runtime = new RuntimeEngineImpl(factory.newKieSession(), internalTaskService); ((RuntimeEngineImpl) runtime).setManager(this); configureRuntimeOnTaskService(internalTaskService, runtime); registerDisposeCallback(runtime, new DisposeSessionTransactionSynchronization(this, runtime)); registerDisposeCallback(runtime, new DestroySessionTransactionSynchronization(runtime.getKieSession())); registerItems(runtime); attachManager(runtime); } else { runtime = new RuntimeEngineImpl(context, new PerRequestInitializer()); ((RuntimeEngineImpl) runtime).setManager(this); } local.get().put(identifier, runtime); ((ExecutionErrorManagerImpl)executionErrorManager).createHandler(); return runtime; } @Override public void signalEvent(String type, Object event) { RuntimeEngine runtimeEngine = getRuntimeEngine(EmptyContext.get()); runtimeEngine.getKieSession().signalEvent(type, event); if (canDispose(runtimeEngine)) { disposeRuntimeEngine(runtimeEngine); } } @Override public void validate(KieSession ksession, Context<?> context) throws IllegalStateException { if (isClosed()) { throw new IllegalStateException("Runtime manager " + identifier + " is already closed"); } RuntimeEngine runtimeInUse = local.get().get(identifier); if (runtimeInUse == null || runtimeInUse.getKieSession().getIdentifier() != ksession.getIdentifier()) { throw new IllegalStateException("Invalid session was used for this context " + context); } } @Override public void disposeRuntimeEngine(RuntimeEngine runtime) { if (isClosed()) { throw new IllegalStateException("Runtime manager " + identifier + " is already closed"); } try { if (canDispose(runtime)) { local.get().remove(identifier); ((ExecutionErrorManagerImpl)executionErrorManager).closeHandler(); try { if (canDestroy(runtime)) { runtime.getKieSession().destroy(); } else { if (runtime instanceof Disposable) { ((Disposable) runtime).dispose(); } } } catch (Exception e) { // do nothing if (runtime instanceof Disposable) { ((Disposable) runtime).dispose(); } } } } catch (Exception e) { local.get().remove(identifier); ((ExecutionErrorManagerImpl)executionErrorManager).closeHandler(); throw new RuntimeException(e); } } @Override public void softDispose(RuntimeEngine runtimeEngine) { super.softDispose(runtimeEngine); local.get().remove(identifier); } @Override public void close() { try { if (!(taskServiceFactory instanceof LocalTaskServiceFactory)) { // if it's CDI based (meaning single application scoped bean) we need to unregister context removeRuntimeFromTaskService(); } } catch(Exception e) { // do nothing } super.close(); factory.close(); } public SessionFactory getFactory() { return factory; } public void setFactory(SessionFactory factory) { this.factory = factory; } public TaskServiceFactory getTaskServiceFactory() { return taskServiceFactory; } public void setTaskServiceFactory(TaskServiceFactory taskServiceFactory) { this.taskServiceFactory = taskServiceFactory; } @Override public void init() { TaskContentRegistry.get().addMarshallerContext(getIdentifier(), new ContentMarshallerContext(environment.getEnvironment(), environment.getClassLoader())); configureRuntimeOnTaskService((InternalTaskService) taskServiceFactory.newTaskService(), null); } private class PerRequestInitializer implements RuntimeEngineInitlializer { @Override public KieSession initKieSession(Context<?> context, InternalRuntimeManager manager, RuntimeEngine engine) { RuntimeEngine inUse = local.get().get(identifier); if (inUse != null && ((RuntimeEngineImpl) inUse).internalGetKieSession() != null) { return inUse.getKieSession(); } KieSession ksession = factory.newKieSession(); ((RuntimeEngineImpl)engine).internalSetKieSession(ksession); registerDisposeCallback(engine, new DisposeSessionTransactionSynchronization(manager, engine)); registerDisposeCallback(engine, new DestroySessionTransactionSynchronization(ksession)); registerItems(engine); attachManager(engine); return ksession; } @Override public TaskService initTaskService(Context<?> context, InternalRuntimeManager manager, RuntimeEngine engine) { InternalTaskService internalTaskService = (InternalTaskService) taskServiceFactory.newTaskService(); configureRuntimeOnTaskService(internalTaskService, engine); return internalTaskService; } } }