/* * 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 org.drools.core.command.impl.ExecutableCommand; import org.drools.core.command.impl.RegistryContext; import org.drools.core.common.InternalKnowledgeRuntime; import org.drools.core.runtime.process.InternalProcessRuntime; import org.drools.persistence.api.TransactionManager; import org.jbpm.process.instance.ProcessRuntimeImpl; import org.jbpm.runtime.manager.impl.error.ExecutionErrorManagerImpl; 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.internal.runtime.manager.Disposable; import org.kie.internal.runtime.manager.SessionFactory; import org.kie.internal.runtime.manager.TaskServiceFactory; import org.kie.internal.task.api.ContentMarshallerContext; import org.kie.internal.task.api.InternalTaskService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** * This RuntimeManager is backed by a "Singleton" strategy, meaning that only one <code>RuntimeEngine</code> instance will * exist for for the given RuntimeManager instance. The RuntimeEngine will be synchronized to make sure it will work * properly in multi-threaded environments. However, this might cause some performance issues due to sequential execution. * <br/> * An important aspect of this manager is that it will persists it's identifier as a temporary file to keep track of the * <code>KieSession</code> it was using to maintain its state: for example, the session state including (drools) facts, etc. * The mentioned file is named as follows:<br> * <code>manager.getIdentifier()-jbpmSessionId.ser</code> * For example, for default named manager it will be:<br/> * default-singleton-jbpmSessionId.ser * <br/> * The location of the file will be one of the following, it is resolved in below order: * <ul> * <li>system property named: jbpm.data.dir</li> * <li>system property named: jboss.server.data.dir - shall be used by default on JBoss AS</li> * <li>system property named: java.io.tmpdir</li> * </ul> * In case there is a need to reset the state, simply removing of the *-jbpm.SessionId.ser from the mentioned location * will do the trick. */ public class SingletonRuntimeManager extends AbstractRuntimeManager { private static final Logger logger = LoggerFactory.getLogger(SingletonRuntimeManager.class); private RuntimeEngine singleton; private SessionFactory factory; private TaskServiceFactory taskServiceFactory; public SingletonRuntimeManager() { super(null, null); // no-op just for cdi, spring and other frameworks } public SingletonRuntimeManager(RuntimeEnvironment environment, SessionFactory factory, TaskServiceFactory taskServiceFactory, String identifier) { super(environment, identifier); this.factory = factory; this.taskServiceFactory = taskServiceFactory; this.identifier = identifier; } public void init() { // TODO should we proxy/wrap the ksession so we capture dispose.destroy method calls? String location = getLocation(); Long knownSessionId = getPersistedSessionId(location, identifier); InternalTaskService internalTaskService = (InternalTaskService) taskServiceFactory.newTaskService(); boolean owner = false; TransactionManager tm = null; if (environment.usePersistence()) { tm = getTransactionManagerInternal(environment.getEnvironment()); owner = tm.begin(); } try { if (knownSessionId > 0) { try { this.singleton = new SynchronizedRuntimeImpl(factory.findKieSessionById(knownSessionId), internalTaskService); } catch (RuntimeException e) { // in case session with known id was found } } if (this.singleton == null) { this.singleton = new SynchronizedRuntimeImpl(factory.newKieSession(), internalTaskService); persistSessionId(location, identifier, singleton.getKieSession().getIdentifier()); } ((RuntimeEngineImpl) singleton).setManager(this); TaskContentRegistry.get().addMarshallerContext(getIdentifier(), new ContentMarshallerContext(environment.getEnvironment(), environment.getClassLoader())); configureRuntimeOnTaskService(internalTaskService, singleton); registerItems(this.singleton); attachManager(this.singleton); this.registry.register(this); if (tm != null) { tm.commit(owner); } } catch (Exception e) { if (tm != null) { tm.rollback(owner); } throw new RuntimeException("Exception while initializing runtime manager " + this.identifier, e); } } @Override public void activate() { super.activate(); this.singleton.getKieSession().execute(new ExecutableCommand<Void>() { private static final long serialVersionUID = 4698203316007668876L; @Override public Void execute(org.kie.api.runtime.Context context) { KieSession ksession = ((RegistryContext) context).lookup( KieSession.class ); ksession.getEnvironment().set("Active", true); InternalProcessRuntime processRuntime = ((InternalKnowledgeRuntime) ksession).getProcessRuntime(); ((ProcessRuntimeImpl) processRuntime).initProcessEventListeners(); ((ProcessRuntimeImpl) processRuntime).initStartTimers(); return null; } }); } @Override public void deactivate() { super.deactivate(); this.singleton.getKieSession().execute(new ExecutableCommand<Void>() { private static final long serialVersionUID = 8099201526203340191L; @Override public Void execute(org.kie.api.runtime.Context context) { KieSession ksession = ((RegistryContext) context).lookup( KieSession.class ); ksession.getEnvironment().set("Active", false); InternalProcessRuntime processRuntime = ((InternalKnowledgeRuntime) ksession).getProcessRuntime(); ((ProcessRuntimeImpl) processRuntime).removeProcessEventListeners(); return null; } }); } @SuppressWarnings("rawtypes") @Override public RuntimeEngine getRuntimeEngine(Context context) { if (isClosed()) { throw new IllegalStateException("Runtime manager " + identifier + " is already closed"); } checkPermission(); ((ExecutionErrorManagerImpl)executionErrorManager).createHandler(); // always return the same instance return this.singleton; } @Override public void signalEvent(String type, Object event) { if (isClosed()) { throw new IllegalStateException("Runtime manager " + identifier + " is already closed"); } checkPermission(); this.singleton.getKieSession().signalEvent(type, event); } @Override public void validate(KieSession ksession, Context<?> context) throws IllegalStateException { if (isClosed()) { throw new IllegalStateException("Runtime manager " + identifier + " is already closed"); } if (this.singleton != null && this.singleton.getKieSession().getIdentifier() != ksession.getIdentifier()) { throw new IllegalStateException("Invalid session was used for this context " + context); } } @Override public void disposeRuntimeEngine(RuntimeEngine runtime) { // no-op, singleton session is always active ((ExecutionErrorManagerImpl)executionErrorManager).closeHandler(); } @Override public void close() { if (singleton == null) { return; } super.close(); // dispose singleton session only when manager is closing try { removeRuntimeFromTaskService(); } catch (UnsupportedOperationException e) { logger.debug("Exception while closing task service, was it initialized? {}", e.getMessage()); } if (this.singleton instanceof Disposable) { ((Disposable) this.singleton).dispose(); } factory.close(); this.singleton = null; } /** * Retrieves session id from serialized file named jbpmSessionId.ser from given location. * @param location directory where jbpmSessionId.ser file should be * @param identifier of the manager owning this ksessionId * @return sessionId if file was found otherwise 0 */ protected Long getPersistedSessionId(String location, String identifier) { File sessionIdStore = new File(location + File.separator + identifier+ "-jbpmSessionId.ser"); if (sessionIdStore.exists()) { Long knownSessionId = null; FileInputStream fis = null; ObjectInputStream in = null; try { fis = new FileInputStream(sessionIdStore); in = new ObjectInputStream(fis); Object tmp = in.readObject(); if (tmp instanceof Integer) { tmp = new Long((Integer) tmp); } knownSessionId = (Long) tmp; return knownSessionId.longValue(); } catch (Exception e) { return 0L; } finally { if (in != null) { try { in.close(); } catch (IOException e) { } } if (fis != null) { try { fis.close(); } catch (IOException e) { } } } } else { return 0L; } } /** * Stores gives ksessionId in a serialized file in given location under jbpmSessionId.ser file name * @param location directory where serialized file should be stored * @param identifier of the manager owning this ksessionId * @param ksessionId value of ksessionId to be stored */ protected void persistSessionId(String location, String identifier, Long ksessionId) { if (location == null) { return; } FileOutputStream fos = null; ObjectOutputStream out = null; try { fos = new FileOutputStream(location + File.separator + identifier + "-jbpmSessionId.ser"); out = new ObjectOutputStream(fos); out.writeObject(Long.valueOf(ksessionId)); out.close(); } catch (IOException ex) { // logger.warn("Error when persisting known session id", ex); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { } } if (out != null) { try { out.close(); } catch (IOException e) { } } } } protected String getLocation() { String location = System.getProperty("jbpm.data.dir", System.getProperty("jboss.server.data.dir")); if (location == null) { location = System.getProperty("java.io.tmpdir"); } return location; } 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; } }