ServiceProcessManager.java
001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002  *
003  * Copyright 2000-2014,  Michael Meyling <mime@qedeq.org>.
004  *
005  * "Hilbert II" is free software; you can redistribute
006  * it and/or modify it under the terms of the GNU General Public
007  * License as published by the Free Software Foundation; either
008  * version 2 of the License, or (at your option) any later version.
009  *
010  * This program is distributed in the hope that it will be useful,
011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013  * GNU General Public License for more details.
014  */
015 
016 package org.qedeq.kernel.bo.service.internal;
017 
018 import java.util.ArrayList;
019 import java.util.List;
020 
021 import org.qedeq.base.io.Parameters;
022 import org.qedeq.base.trace.Trace;
023 import org.qedeq.kernel.bo.KernelContext;
024 import org.qedeq.kernel.bo.common.ModuleServiceResult;
025 import org.qedeq.kernel.bo.common.QedeqBo;
026 import org.qedeq.kernel.bo.common.ServiceJob;
027 import org.qedeq.kernel.bo.job.InternalModuleServiceCallImpl;
028 import org.qedeq.kernel.bo.job.InternalServiceJobImpl;
029 import org.qedeq.kernel.bo.log.QedeqLog;
030 import org.qedeq.kernel.bo.module.InternalModuleServiceCall;
031 import org.qedeq.kernel.bo.module.InternalServiceJob;
032 import org.qedeq.kernel.bo.module.KernelQedeqBo;
033 import org.qedeq.kernel.bo.module.ModuleArbiter;
034 import org.qedeq.kernel.bo.service.basis.ModuleServiceExecutor;
035 import org.qedeq.kernel.bo.service.basis.ModuleServicePlugin;
036 import org.qedeq.kernel.bo.service.basis.ModuleServicePluginExecutor;
037 import org.qedeq.kernel.se.common.ModuleService;
038 import org.qedeq.kernel.se.common.Service;
039 import org.qedeq.kernel.se.visitor.InterruptException;
040 
041 /**
042  * Manage all known processes.
043  */
044 public class ServiceProcessManager {
045 
046     /** This class. */
047     private static final Class CLASS = ServiceProcessManager.class;
048 
049     /** Stores all running processes. */
050     private final List processes = new ArrayList();
051 
052     /** Stores some finished processes. FIXME 20130408 m31: use! */
053     private final List finished = new ArrayList();
054 
055     /** Stores all calls. */
056     private final List calls = new ArrayList();
057 
058     /** Manage all known plugins. */
059     private final PluginManager pluginManager;
060 
061     /** Manage synchronized module access. */
062     private final ModuleArbiter arbiter;
063 
064     /**
065      * Constructor.
066      *
067      @param   pluginManager   Collects process information.
068      @param   arbiter         For module access synchronization.
069      */
070     public ServiceProcessManager(final PluginManager pluginManager, final ModuleArbiter arbiter) {
071         this.pluginManager = pluginManager;
072         this.arbiter = arbiter;
073     }
074 
075 
076     /**
077      * Get all service processes.
078      *
079      @return  All service processes.
080      */
081     public synchronized ServiceJob[] getServiceProcesses() {
082         return (ServiceJob[]) processes.toArray(new ServiceJob[] {});
083     }
084 
085     /**
086      * Get all running service processes. But remember a running process might currently
087      * be blocked.
088      *
089      @return  All service running processes.
090      */
091     public synchronized ServiceJob[] getRunningServiceProcesses() {
092         final ArrayList result = new ArrayList(processes);
093         for (int i = 0; i < result.size()) {
094             if (!((ServiceJobresult.get(i)).isRunning()) {
095                 result.remove(i);
096             else {
097                 i++;
098             }
099         }
100         return (ServiceJob[]) result.toArray(new ServiceJob[] {});
101     }
102 
103     /**
104      * Create service call. Might block further execution, because an exclusive access to given module is given.
105      *
106      @param   service             The service that runs in current thread.
107      @param   qedeq               QEDEQ module for service.
108      @param   configParameters    Config parameters for the service.
109      @param   parameters          Parameter for this service call.
110      @param   process             We run in this process.
111      @return  Created service call. Never <code>null</code> (if no {@link InterruptException} occurred).
112      @throws  InterruptException  User canceled call.
113      */
114     public InternalModuleServiceCallImpl createServiceCall(final Service service,
115             final QedeqBo qedeq, final Parameters configParameters, final Parameters parameters,
116             final InternalServiceJob processthrows InterruptException {
117         if (!process.isRunning()) { // should not occur
118             throw new RuntimeException("Service process is not running any more.");
119         }
120         if (!process.getThread().isAlive()) {
121             throw new RuntimeException("thread is already dead");
122         }
123         final InternalModuleServiceCallImpl call = new InternalModuleServiceCallImpl(service, qedeq, configParameters,
124             parameters, process, process.getModuleServiceCall());
125         synchronized (this) {
126             calls.add(call);
127         }
128         process.setInternalServiceCall(call);
129         arbiter.lockRequiredModule(call);
130         return call;
131     }
132 
133     /**
134      * End service call by unlocking previously locked module.
135      *
136      @param   call    End this call, which should be finished, interrupted or halted before.
137      */
138     public void endServiceCall(final InternalModuleServiceCall call) {
139         arbiter.unlockRequiredModule(call);
140     }
141 
142     /**
143      * Remove all service processes. All processes are also terminated via interruption.
144      */
145     public synchronized void terminateAndRemoveAllServiceProcesses() {
146         terminateAllServiceProcesses();
147         processes.clear();
148         finished.clear();
149         calls.clear();
150     }
151 
152     /**
153      * Terminate all service processes.
154      */
155     public synchronized void terminateAllServiceProcesses() {
156         for (int i = 0; i < processes.size(); i++) {
157             final ServiceJob proc = (ServiceJobprocesses.get(i);
158             proc.interrupt();
159         }
160     }
161 
162     public synchronized InternalServiceJobImpl createServiceProcess(final String action) {
163         final InternalServiceJobImpl process = new InternalServiceJobImpl(arbiter, action);
164         processes.add(process);
165         return process;
166     }
167 
168     public ModuleServiceResult executeService(final ModuleService service, final ModuleServiceExecutor executor,
169             final QedeqBo qedeq, final InternalServiceJob processthrows InterruptException {
170         final String method = "executePlugin(String, KernelQedeqBo, Object)";
171         if (process == null) {
172             throw new NullPointerException("ServiceProcess must not be null");
173         }
174         final Parameters configParameters = KernelContext.getInstance().getConfig().getServiceEntries(service);
175         InternalModuleServiceCallImpl call = null;
176         try {
177             call = createServiceCall(service, qedeq, configParameters, Parameters.EMPTY, process);
178             executor.executeService(call);
179             return call.getServiceResult();
180         catch (final RuntimeException e) {
181             final String msg = service.getServiceAction() " failed with a runtime exception.";
182             Trace.fatal(CLASS, this, method, msg, e);
183             QedeqLog.getInstance().logFailureReply(msg, qedeq.getUrl(), e.getMessage());
184             if (call != null) {
185                 call.finishError(msg + " " + e.getMessage());
186             }
187             process.setFailureState();
188             return call != null ? call.getServiceResult() null;
189         catch (final InterruptException e) {
190             final String msg = service.getServiceAction() " was canceled by user.";
191             QedeqLog.getInstance().logFailureReply(msg, qedeq.getUrl(), e.getMessage());
192             if (call != null) {
193                 call.interrupt();
194             }
195             process.setInterruptedState();
196             throw e;
197         finally {
198             endServiceCall(call);
199         }
200     }
201 
202     /**
203      * Execute a plugin on an QEDEQ module.
204      *
205      @param   id          Plugin to use.
206      @param   qedeq       QEDEQ module to work on.
207      @param   data        Process parameters.
208      @param   process     Process. Must not be <code>null</code>..
209      @return  Plugin      Specific result object. Might be <code>null</code>.
210      @throws  InterruptException  User interrupt occurred.
211      @throws  RuntimeException    Plugin unknown or process is not running any more.
212      */
213     public Object executePlugin(final String id, final KernelQedeqBo qedeq, final Object data,
214             final InternalServiceJob processthrows InterruptException {
215         final String method = "executePlugin(String, KernelQedeqBo, Object, InternalServiceJob)";
216         final ModuleServicePlugin plugin = pluginManager.getPlugin(id);
217         if (plugin == null) {
218             final String message = "Kernel does not know about plugin: ";
219             final RuntimeException e = new RuntimeException(message + id);
220             Trace.fatal(CLASS, this, method, message + id,
221                 e);
222             throw e;
223         }
224         final Parameters configParameters = KernelContext.getInstance().getConfig().getServiceEntries(plugin);
225         if (!process.isRunning()) {
226             // TODO 20140124 m31: but if it was interrupted we want to throw a InterrruptException
227             final String message = "Process " + process.getId() " was already finished: "
228                 + process.getExecutionActionDescription();
229             final RuntimeException e = new RuntimeException(message + id);
230             Trace.fatal(CLASS, this, method, message + id,
231                 e);
232             throw e;
233         }
234         InternalModuleServiceCallImpl call = null;
235         try {
236             call = createServiceCall(plugin, qedeq, configParameters, Parameters.EMPTY,
237                 process);
238             final ModuleServicePluginExecutor exe = plugin.createExecutor(qedeq, configParameters);
239             call.setServiceCompleteness(exe);
240             final Object result = exe.executePlugin(call, data);
241             if (exe.getInterrupted()) {
242                 call.interrupt();
243                 throw new InterruptException(qedeq.getModuleAddress().createModuleContext());
244             else {
245                 call.finishOk();
246                 process.setInternalServiceCall((InternalModuleServiceCallcall.getParentServiceCall());
247             }
248             return result;
249         catch (final RuntimeException e) {
250             final String msg = plugin.getServiceAction() " failed with a runtime exception.";
251             Trace.fatal(CLASS, this, method, msg, e);
252             QedeqLog.getInstance().logFailureReply(msg, qedeq.getUrl(), e.getMessage());
253             if (call != null) {
254                 call.finishError(msg + ": " + e.getMessage());
255             }
256             return null;
257         catch (final InterruptException e) {
258             final String msg = plugin.getServiceAction() " was canceled by user.";
259             QedeqLog.getInstance().logFailureReply(msg, qedeq.getUrl(), e.getMessage());
260             if (call != null) {
261                 call.interrupt();
262             }
263             throw e;
264         finally {
265             endServiceCall(call);
266         }
267     }
268 
269     /**
270      * Create a service job for executing a plugin.
271      *
272      @param   id          Plugin to use.
273      @return  Process.
274      @throws  RuntimeException    Plugin unknown.
275      */
276     public InternalServiceJob createServiceJob(final String id) {
277         final ModuleServicePlugin plugin = pluginManager.getPlugin(id);
278         if (plugin == null) {
279             final String message = "Kernel does not know about plugin: ";
280             final RuntimeException e = new RuntimeException(message + id);
281             Trace.fatal(CLASS, this, "createServiceJob", message + id, e);
282             throw e;
283         }
284         return createServiceProcess(plugin.getServiceAction());
285     }
286 
287 }