Merge lp:~slub.team/goobi-production/mq-interface-to-create-and-finalise-processes into lp:goobi-production/1.8
- mq-interface-to-create-and-finalise-processes
- Merge into 1.8
Proposed by
Ralf Claussnitzer
Status: | Merged |
---|---|
Merged at revision: | 82 |
Proposed branch: | lp:~slub.team/goobi-production/mq-interface-to-create-and-finalise-processes |
Merge into: | lp:goobi-production/1.8 |
Diff against target: |
2000 lines (+1629/-93) 20 files modified
WEB-INF/web.xml (+5/-0) config/GoobiConfig.properties (+35/-0) doc/implementing_active_mq_web_services_for_goobi.txt (+80/-0) doc/web_service_to_create_new_processes.txt (+104/-0) src-dubious/dubious/sub/goobi/helper/Page.java (+1/-1) src/de/sub/goobi/config/ConfigMain.java (+14/-19) src/de/sub/goobi/config/DigitalCollections.java (+77/-0) src/de/sub/goobi/forms/AktuelleSchritteForm.java (+1/-1) src/de/sub/goobi/forms/ProzesskopieForm.java (+10/-32) src/de/sub/goobi/helper/Helper.java (+54/-34) src/de/sub/goobi/helper/UghHelper.java (+15/-4) src/de/sub/goobi/helper/WebDav.java (+2/-1) src/de/sub/goobi/helper/enums/ReportLevel.java (+36/-0) src/org/goobi/mq/ActiveMQDirector.java (+231/-0) src/org/goobi/mq/ActiveMQProcessor.java (+161/-0) src/org/goobi/mq/MapMessageObjectReader.java (+242/-0) src/org/goobi/mq/WebServiceResult.java (+88/-0) src/org/goobi/mq/processors/CreateNewProcessProcessor.java (+375/-0) src/org/goobi/mq/processors/FinaliseStepProcessor.java (+97/-0) src/org/goobi/production/flow/statistics/hibernate/FilterHelper.java (+1/-1) |
To merge this branch: | bzr merge lp:~slub.team/goobi-production/mq-interface-to-create-and-finalise-processes |
Related bugs: | |
Related blueprints: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ralf Claussnitzer (community) | Approve | ||
Review via email: mp+117031@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Ralf Claussnitzer (ralf-claussnitzer-deactivatedaccount) : | # |
review:
Approve
- 98. By Ralf Claussnitzer
-
add missing license header
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'WEB-INF/web.xml' |
2 | --- WEB-INF/web.xml 2012-04-20 06:57:46 +0000 |
3 | +++ WEB-INF/web.xml 2012-07-27 10:58:19 +0000 |
4 | @@ -215,6 +215,11 @@ |
5 | <listener-class>org.goobi.production.ImageIOInitializer</listener-class> |
6 | </listener> |
7 | |
8 | + <!-- Listener to run ActiveMQ services --> |
9 | + <listener> |
10 | + <listener-class>org.goobi.mq.ActiveMQDirector</listener-class> |
11 | + </listener> |
12 | + |
13 | <!-- |
14 | xml-Rpc-Server starten <listener> <listener-class> |
15 | de.sub.goobi.XmlRpc.Listener </listener-class> </listener> |
16 | |
17 | === modified file 'config/GoobiConfig.properties' |
18 | --- config/GoobiConfig.properties 2012-07-05 13:00:48 +0000 |
19 | +++ config/GoobiConfig.properties 2012-07-27 10:58:19 +0000 |
20 | @@ -184,6 +184,41 @@ |
21 | # Password encryption SHA or MD5 |
22 | ldap_encryption=SHA |
23 | |
24 | +# ----------------------------------- |
25 | +# ActiveMQ web services |
26 | +# ----------------------------------- |
27 | + |
28 | +# If you want to use Goobi's ActiveMQ web servic interface, set the host here |
29 | +#activeMQ.hostURL=tcp://localhost:61616 |
30 | + |
31 | +# You can provide a topic that Goobi reports results and status messages to |
32 | +#activeMQ.results.topic=GoobiProduction.ResultMessages.Topic |
33 | + |
34 | +# By default, Goobi instructs the server to keep status messages for a |
35 | +# equivalent of 7 days. You can change this value in (milliseconds to) meet |
36 | +# your needs. 0 will disable the deletion of messages completely. (However, |
37 | +# the messages will only available on the Active MQ server if your |
38 | +# TopicSubscriber is online with the Active MQ server before the message is |
39 | +# sent. You migth therefore consider to configure the timeToLive for offline |
40 | +# usage within the Active MQ server’s activemq.xml file by adding a |
41 | +# |
42 | +# <policyEntry topic="GoobiProduction.ResultMessages.Topic"> |
43 | +# <subscriptionRecoveryPolicy> |
44 | +# <timedSubscriptionRecoveryPolicy recoverDuration="604800000" /> |
45 | +# </subscriptionRecoveryPolicy> |
46 | +# </policyEntry> |
47 | +# |
48 | +# block inside the <policyEntries>-Element. “recoverDuration” has to be given |
49 | +# in milliseconds here, too.) |
50 | +#activeMQ.results.timeToLive=604800000 |
51 | + |
52 | +# You can provide a queue from which messages are read to create new processes |
53 | +#activeMQ.createNewProcess.queue=GoobiProduction.CreateNewProcesses.Queue |
54 | + |
55 | +# You can provide a queue from which messages are read to finalise steps |
56 | +#activeMQ.finaliseStep.queue=GoobiProduction.FinaliseStep.Queue |
57 | + |
58 | + |
59 | ################################## |
60 | # DO NOT CHANGE THE OPTIONS BELOW! |
61 | ################################## |
62 | |
63 | === added directory 'doc' |
64 | === added file 'doc/implementing_active_mq_web_services_for_goobi.txt' |
65 | --- doc/implementing_active_mq_web_services_for_goobi.txt 1970-01-01 00:00:00 +0000 |
66 | +++ doc/implementing_active_mq_web_services_for_goobi.txt 2012-07-27 10:58:19 +0000 |
67 | @@ -0,0 +1,80 @@ |
68 | + Implementing Active MQ web services for Goobi |
69 | + |
70 | +Active Message Queue is an open source Java Messaging (JMS) implementation |
71 | +provided by the Apache Software Foundation. It is intended to be used to |
72 | +to connect software components in a flexible way. The core is the Active MQ |
73 | +server which can be pictured like a post office. The mail boxes are named |
74 | +“queue” or “topic”. Queues work as expected: A producer sends a message where |
75 | +a consumer can pick it up. Topics can be pictured as black boards: The main |
76 | +difference is: A message read from a queue is removed from the queue. A message |
77 | +read from a topic is still available to others. Consumer clients can actively |
78 | +check the server or may register listeners with the server to be notified of |
79 | +new messages. |
80 | + |
81 | +This behaviour has already been implemented to Goobi: The org.goobi.mq. |
82 | +ActiveMQDirector is a ServletContextListener which is registered in web.xml. |
83 | +On application startup, it registers all consumers from its “services” variable |
84 | +to the server configured in “activeMQ.hostURL”. |
85 | + |
86 | +The elements of this variable are classes extending the abstract class |
87 | +ActiveMQProcessor. This class implements the MessageListener and provides |
88 | +facilities to handle exceptions and to store the consumer which is required on |
89 | +shutdown to disconnect. |
90 | + |
91 | +To implement another web service processor, you have to implement a class which |
92 | +extends ActiveMQProcessor and implements its abstract void process(MapMessage). |
93 | +Here is the right place to do whatever your processor is intended to do. There |
94 | +is a class MapMessageObjectReader which shall be used to type safe retrieve |
95 | +complex objects from MapMessages. You must add your new class to the “services” |
96 | +variable of ActiveMQDirector then. |
97 | + |
98 | +The Goobi server administrator shall be in control which processors are being |
99 | +started, and which queue names they listen on. Implementation of this |
100 | +configurability is designed this way: The implementing class must pass its |
101 | +queue name to the constructor of the parent class. This is done by implementing |
102 | +the constructor like in the following skeleton. If the queue name is not |
103 | +configured, it will return null which will prevent the ActiveMQDirector from |
104 | +registering it to the server. Inside the class, the queue name is available in |
105 | +the global variable “queueName” which is set by the parent class. The |
106 | +implementation may use arbitrary “activeMQ.myService.*” entries in |
107 | +GoobiConfig.properties for configuration. |
108 | + |
109 | +---------------------[ Service processor skeleton sample ]--------------------- |
110 | +package org.goobi.mq.processores; |
111 | + |
112 | +import org.goobi.mq.*; |
113 | +import de.sub.goobi.config.ConfigMain; |
114 | +import de.sub.goobi.helper.enums.ReportLevel; |
115 | + |
116 | +public class MyServiceProcessor extends ActiveMQProcessor { |
117 | + |
118 | + public MyServiceProcessor() { |
119 | + super(ConfigMain.getParameter("activeMQ.myService.queue", null)); |
120 | + } |
121 | + |
122 | + @Override |
123 | + protected void process(MapMessageObjectReader args) throws Exception { |
124 | + // TODO Auto-generated method stub |
125 | + } |
126 | +} |
127 | +------------------------------------------------------------------------------- |
128 | + |
129 | +Responses from processors are designed to be handled as WebServiceResult |
130 | +objects. Those objects are MapMessages which send themselves to a topic |
131 | +configured in “activeMQ.results.topic”. They consist of the Strings “queue” |
132 | +(the name of the queue the job ticket was sent to), “id” (a String “id” in |
133 | +the MapMessage which is mandatory), “level” and an optional “message”. When |
134 | +designing the MapMessage layout to parameterise your web service processor, |
135 | +please keep in mind that a String element “id” is mandatory. |
136 | + |
137 | +If process() terminates without error, it is meant to have done its job |
138 | +successfully and a WebServiceResult with level “success” will be sent. If |
139 | +process() returns an exception, a WebServiceResult with level “fatal” will be |
140 | +sent. The exception will be returned as the “message” String. You may also use |
141 | +the WebServiceResult class to send messages with the levels “error”, “warn”, |
142 | +“info”, “debug”, “verbose” and “ludicrous” which are meant to be informative |
143 | +only: |
144 | + new WebServiceResult(queueName, args.getMandatoryString("id"), |
145 | + ReportLevel.INFO, "Remote host is down, trying again later.") |
146 | + .send(); |
147 | + |
148 | |
149 | === added file 'doc/web_service_to_create_new_processes.txt' |
150 | --- doc/web_service_to_create_new_processes.txt 1970-01-01 00:00:00 +0000 |
151 | +++ doc/web_service_to_create_new_processes.txt 2012-07-27 10:58:19 +0000 |
152 | @@ -0,0 +1,104 @@ |
153 | + Web service to create new processes |
154 | + |
155 | +Goobi.Production is equiped with a web service interface to automatically |
156 | +create new processes based on a given template. This allows the digitization |
157 | +process to be initiated from outside the application, for example by assigning |
158 | +a new digital ID to a record in a library catalogue (or—at choice of the |
159 | +library—by duplicating a record and assigning a new digital ID to the |
160 | +duplicate) and then running a script. |
161 | + |
162 | +The web service infrastructure is providet by an Active MQ server (see |
163 | +http://activemq.apache.org/ for details) which needs to be downloaded and |
164 | +started. Without further configuration, it provides everything necessary on |
165 | +port 61616 of the machine in question. |
166 | + |
167 | +The “activeMQ.hostURL” must be set in GoobiConfig.properties to point to this |
168 | +server. The “activeMQ.createNewProcess.queue” must be set to point to a queue |
169 | +of your choice where Goobi.Production shall pick up orders to create new |
170 | +processes. |
171 | + |
172 | +Orders must be javax.jms.MapMessage objects with the following key-value-pairs |
173 | +provided: |
174 | + |
175 | + String template |
176 | + name of the process template to use |
177 | + String opac |
178 | + Cataloge to use for lookup |
179 | + String field |
180 | + Field to look into, usually 12 (PPN) |
181 | + String value |
182 | + Value to look for, id of physical medium |
183 | + String id |
184 | + Ticket ID (used in log responses) |
185 | + List<String> collections |
186 | + Collections to be selected |
187 | + Map<String, String> userFields (optional) |
188 | + May be used to populates AdditionalField entries |
189 | + |
190 | +Here is a sample java client to do the job. It expects to be passed from the |
191 | +command line the Active MQ host (e.g. tcp://localhost:61616), the queue name |
192 | +and the parameters as listed above. |
193 | + |
194 | +To run this application, the following JARs from the ActiveMQ server’s /lib |
195 | +folder are required on the classpath: |
196 | + * activemq-core |
197 | + * geronimo-j2ee-management_1.1_spec |
198 | + * genonimo-jms_1.1_spec |
199 | + * log4j |
200 | + * slf4j-api |
201 | + * slf4j-log4j12 |
202 | + |
203 | +--------------------------------[ Main.java ]---------------------------------- |
204 | +import java.util.*; |
205 | +import javax.jms.*; |
206 | +import org.apache.activemq.ActiveMQConnectionFactory; |
207 | + |
208 | +public class Main { |
209 | + public static int main(String[] args) { try { |
210 | + |
211 | + // Check arguments |
212 | + if (args.length < 8 || (args.length % 2) != 0) { |
213 | + System.out.println("Parameters: Active MQ host, queue name, " |
214 | + + "template name, opac name,"); |
215 | + System.out.println(" no. of search field, search " |
216 | + + "string, digital id, collection name,"); |
217 | + System.out.println(" [additional details field, " |
218 | + + "value, [add. details field, value, [..."); |
219 | + return 1; |
220 | + } |
221 | + |
222 | + // Connect to server |
223 | + Connection connection = new ActiveMQConnectionFactory(args[0]) |
224 | + .createConnection(); |
225 | + connection.start(); |
226 | + Session session = connection.createSession(false, |
227 | + Session.AUTO_ACKNOWLEDGE); |
228 | + Destination destination = session.createQueue(args[1]); |
229 | + MessageProducer producer = session.createProducer(destination); |
230 | + producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); |
231 | + |
232 | + // Create job ticket |
233 | + MapMessage message = session.createMapMessage(); |
234 | + message.setString("template", args[2]); |
235 | + message.setString("opac", args[3]); |
236 | + message.setString("field", args[4]); |
237 | + message.setString("value", args[5]); |
238 | + message.setString("id", args[6]); |
239 | + List<String> collections = new ArrayList<String>(); |
240 | + collections.add(args[7]); |
241 | + message.setObject("collections", collections); |
242 | + Map<String, String> userFields = new HashMap<String, String>(); |
243 | + for (int i = 8; i < args.length; i += 2) |
244 | + userFields.put(args[i], args[i + 1]); |
245 | + if (userFields.size() != 0) |
246 | + message.setObject("userFields", userFields); |
247 | + |
248 | + // Send job ticket |
249 | + producer.send(message); |
250 | + |
251 | + // Shutdown |
252 | + session.close(); |
253 | + connection.close(); |
254 | + } catch (Exception e) { e.printStackTrace(); return 2; } |
255 | + return 0; |
256 | +} } |
257 | |
258 | === added file 'lib/activemq-core-5.5.1.jar' |
259 | Binary files lib/activemq-core-5.5.1.jar 1970-01-01 00:00:00 +0000 and lib/activemq-core-5.5.1.jar 2012-07-27 10:58:19 +0000 differ |
260 | === added file 'lib/geronimo-j2ee-management_1.1_spec-1.0.1.jar' |
261 | Binary files lib/geronimo-j2ee-management_1.1_spec-1.0.1.jar 1970-01-01 00:00:00 +0000 and lib/geronimo-j2ee-management_1.1_spec-1.0.1.jar 2012-07-27 10:58:19 +0000 differ |
262 | === added file 'lib/geronimo-jms_1.1_spec-1.1.1.jar' |
263 | Binary files lib/geronimo-jms_1.1_spec-1.1.1.jar 1970-01-01 00:00:00 +0000 and lib/geronimo-jms_1.1_spec-1.1.1.jar 2012-07-27 10:58:19 +0000 differ |
264 | === added file 'lib/json-simple-1.1.1.jar' |
265 | Binary files lib/json-simple-1.1.1.jar 1970-01-01 00:00:00 +0000 and lib/json-simple-1.1.1.jar 2012-07-27 10:58:19 +0000 differ |
266 | === modified file 'src-dubious/dubious/sub/goobi/helper/Page.java' |
267 | --- src-dubious/dubious/sub/goobi/helper/Page.java 2012-07-03 13:32:53 +0000 |
268 | +++ src-dubious/dubious/sub/goobi/helper/Page.java 2012-07-27 10:58:19 +0000 |
269 | @@ -47,7 +47,7 @@ |
270 | public Page(Criteria criteria, int page) { |
271 | this.page = page; |
272 | LoginForm login = (LoginForm) Helper.getManagedBeanValue("#{LoginForm}"); |
273 | - if (login.getMyBenutzer() == null) |
274 | + if (login == null || login.getMyBenutzer() == null) |
275 | this.pageSize = 10; |
276 | else |
277 | this.pageSize = login.getMyBenutzer().getTabellengroesse().intValue(); |
278 | |
279 | === modified file 'src/de/sub/goobi/config/ConfigMain.java' |
280 | --- src/de/sub/goobi/config/ConfigMain.java 2012-05-30 08:24:19 +0000 |
281 | +++ src/de/sub/goobi/config/ConfigMain.java 2012-07-27 10:58:19 +0000 |
282 | @@ -119,9 +119,9 @@ |
283 | |
284 | |
285 | /** |
286 | - * Ermitteln eines bestimmten Paramters der Konfiguration |
287 | + * Ermitteln eines bestimmten Parameters der Konfiguration |
288 | * |
289 | - * @return Paramter als String |
290 | + * @return Parameter als String |
291 | */ |
292 | public static String getParameter(String inParameter) { |
293 | try { |
294 | @@ -135,10 +135,10 @@ |
295 | |
296 | //TODO: Remove this methods, they are provided by Commons Configuration |
297 | /** |
298 | - * Ermitteln eines bestimmten Paramters der Konfiguration mit Angabe eines |
299 | + * Ermitteln eines bestimmten Parameters der Konfiguration mit Angabe eines |
300 | * Default-Wertes |
301 | * |
302 | - * @return Paramter als String |
303 | + * @return Parameter als String |
304 | */ |
305 | public static String getParameter(String inParameter, String inDefaultIfNull) { |
306 | try { |
307 | @@ -152,18 +152,18 @@ |
308 | |
309 | |
310 | /** |
311 | - * Ermitteln eines boolean-Paramters der Konfiguration, default if missing: false |
312 | + * Ermitteln eines boolean-Parameters der Konfiguration, default if missing: false |
313 | * |
314 | - * @return Paramter als String |
315 | + * @return Parameter als String |
316 | */ |
317 | public static boolean getBooleanParameter(String inParameter) { |
318 | return getBooleanParameter(inParameter, false); |
319 | } |
320 | |
321 | /** |
322 | - * Ermitteln eines boolean-Paramters der Konfiguration |
323 | + * Ermitteln eines boolean-Parameters der Konfiguration |
324 | * |
325 | - * @return Paramter als String |
326 | + * @return Parameter als String |
327 | */ |
328 | public static boolean getBooleanParameter(String inParameter, boolean inDefault) { |
329 | return config.getBoolean(inParameter, inDefault); |
330 | @@ -172,17 +172,12 @@ |
331 | |
332 | |
333 | /** |
334 | - * Ermitteln eines long-Paramters der Konfiguration |
335 | + * Ermitteln eines long-Parameters der Konfiguration |
336 | * |
337 | - * @return Paramter als Long |
338 | + * @return Parameter als Long |
339 | */ |
340 | - public static long getLongParameter(String inParameter, int inDefault) { |
341 | - try { |
342 | - return config.getLong(inParameter, inDefault); |
343 | - } catch (RuntimeException e) { |
344 | - myLogger.error(e); |
345 | - return 0; |
346 | - } |
347 | + public static long getLongParameter(String inParameter, long inDefault) { |
348 | + return config.getLong(inParameter, inDefault); |
349 | } |
350 | |
351 | |
352 | @@ -190,7 +185,7 @@ |
353 | /** |
354 | * Request int-parameter from Configuration |
355 | * |
356 | - * @return Paramter as Int |
357 | + * @return Parameter as Int |
358 | */ |
359 | public static int getIntParameter(String inParameter) { |
360 | return getIntParameter(inParameter, 0); |
361 | @@ -199,7 +194,7 @@ |
362 | /** |
363 | * Request int-parameter from Configuration with default-value |
364 | * |
365 | - * @return Paramter as Int |
366 | + * @return Parameter as Int |
367 | */ |
368 | public static int getIntParameter(String inParameter, int inDefault) { |
369 | try { |
370 | |
371 | === added file 'src/de/sub/goobi/config/DigitalCollections.java' |
372 | --- src/de/sub/goobi/config/DigitalCollections.java 1970-01-01 00:00:00 +0000 |
373 | +++ src/de/sub/goobi/config/DigitalCollections.java 2012-07-27 10:58:19 +0000 |
374 | @@ -0,0 +1,77 @@ |
375 | +/* |
376 | + * This file is part of the Goobi Application - a Workflow tool for the support of |
377 | + * mass digitization. |
378 | + * |
379 | + * Visit the websites for more information. |
380 | + * - http://gdz.sub.uni-goettingen.de |
381 | + * - http://www.goobi.org |
382 | + * - http://launchpad.net/goobi-production |
383 | + * |
384 | + * This program is free software; you can redistribute it and/or modify it under |
385 | + * the terms of the GNU General Public License as published by the Free Software |
386 | + * Foundation; either version 2 of the License, or (at your option) any later |
387 | + * version. |
388 | + * |
389 | + * This program is distributed in the hope that it will be useful, but WITHOUT ANY |
390 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
391 | + * PARTICULAR PURPOSE. See the GNU General Public License for more details. You |
392 | + * should have received a copy of the GNU General Public License along with this |
393 | + * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, |
394 | + * Suite 330, Boston, MA 02111-1307 USA |
395 | + */ |
396 | + |
397 | +package de.sub.goobi.config; |
398 | + |
399 | +import java.io.File; |
400 | +import java.io.FileNotFoundException; |
401 | +import java.io.IOException; |
402 | +import java.util.ArrayList; |
403 | +import java.util.Iterator; |
404 | +import java.util.List; |
405 | + |
406 | +import org.jdom.Document; |
407 | +import org.jdom.Element; |
408 | +import org.jdom.JDOMException; |
409 | +import org.jdom.input.SAXBuilder; |
410 | + |
411 | +import de.sub.goobi.beans.Prozess; |
412 | + |
413 | +public class DigitalCollections { |
414 | + |
415 | + @SuppressWarnings("unchecked") |
416 | + public static List<String> possibleDigitalCollectionsForProcess( |
417 | + Prozess process) throws JDOMException, IOException { |
418 | + |
419 | + List<String> result = new ArrayList<String>(); |
420 | + String filename = ConfigMain.getParameter("KonfigurationVerzeichnis") + "digitalCollections.xml"; |
421 | + if (!(new File(filename).exists())) { |
422 | + throw new FileNotFoundException("File not found: " + filename); |
423 | + } |
424 | + |
425 | + /* Datei einlesen und Root ermitteln */ |
426 | + SAXBuilder builder = new SAXBuilder(); |
427 | + Document doc = builder.build(new File(filename)); |
428 | + Element root = doc.getRootElement(); |
429 | + /* alle Projekte durchlaufen */ |
430 | + List<Element> projekte = root.getChildren(); |
431 | + for (Iterator<Element> iter = projekte.iterator(); iter.hasNext();) { |
432 | + Element projekt = (Element) iter.next(); |
433 | + List<Element> projektnamen = projekt.getChildren("name"); |
434 | + for (Iterator<Element> iterator = projektnamen.iterator(); iterator.hasNext();) { |
435 | + Element projektname = (Element) iterator.next(); |
436 | + |
437 | + /* |
438 | + * wenn der Projektname aufgeführt wird, dann alle Digitalen Collectionen in die Liste |
439 | + */ |
440 | + if (projektname.getText().equalsIgnoreCase(process.getProjekt().getTitel())) { |
441 | + List<Element> myCols = projekt.getChildren("DigitalCollection"); |
442 | + for (Iterator<Element> it2 = myCols.iterator(); it2.hasNext();) { |
443 | + Element col = (Element) it2.next(); |
444 | + result.add(col.getText()); |
445 | + } |
446 | + } |
447 | + } |
448 | + } |
449 | + return result; |
450 | + } |
451 | +} |
452 | |
453 | === modified file 'src/de/sub/goobi/forms/AktuelleSchritteForm.java' |
454 | --- src/de/sub/goobi/forms/AktuelleSchritteForm.java 2012-04-24 13:22:01 +0000 |
455 | +++ src/de/sub/goobi/forms/AktuelleSchritteForm.java 2012-07-27 10:58:19 +0000 |
456 | @@ -109,7 +109,7 @@ |
457 | * --------------------- Vorgangsdatum generell anzeigen? ------------------- |
458 | */ |
459 | LoginForm login = (LoginForm) Helper.getManagedBeanValue("#{LoginForm}"); |
460 | - if (login.getMyBenutzer() != null) |
461 | + if (login != null && login.getMyBenutzer() != null) |
462 | anzeigeAnpassen.put("processDate", login.getMyBenutzer().isConfVorgangsdatumAnzeigen()); |
463 | else |
464 | anzeigeAnpassen.put("processDate", false); |
465 | |
466 | === modified file 'src/de/sub/goobi/forms/ProzesskopieForm.java' |
467 | --- src/de/sub/goobi/forms/ProzesskopieForm.java 2012-05-09 15:19:19 +0000 |
468 | +++ src/de/sub/goobi/forms/ProzesskopieForm.java 2012-07-27 10:58:19 +0000 |
469 | @@ -23,6 +23,7 @@ |
470 | package de.sub.goobi.forms; |
471 | |
472 | import java.io.File; |
473 | +import java.io.FileNotFoundException; |
474 | import java.io.IOException; |
475 | import java.sql.SQLException; |
476 | import java.util.ArrayList; |
477 | @@ -84,6 +85,7 @@ |
478 | import de.sub.goobi.config.ConfigOpac; |
479 | import de.sub.goobi.config.ConfigOpacDoctype; |
480 | import de.sub.goobi.config.ConfigProjects; |
481 | +import de.sub.goobi.config.DigitalCollections; |
482 | import de.sub.goobi.helper.BeanHelper; |
483 | import de.sub.goobi.helper.Helper; |
484 | import de.sub.goobi.helper.Messages; |
485 | @@ -932,46 +934,22 @@ |
486 | return possibleDigitalCollection; |
487 | } |
488 | |
489 | - @SuppressWarnings("unchecked") |
490 | private void initializePossibleDigitalCollections() { |
491 | possibleDigitalCollection = new ArrayList<String>(); |
492 | - String filename = help.getGoobiConfigDirectory() + "digitalCollections.xml"; |
493 | - if (!(new File(filename).exists())) { |
494 | - Helper.setFehlerMeldung("File not found: ", filename); |
495 | - return; |
496 | - } |
497 | - |
498 | - try { |
499 | - /* Datei einlesen und Root ermitteln */ |
500 | - SAXBuilder builder = new SAXBuilder(); |
501 | - Document doc = builder.build(new File(filename)); |
502 | - Element root = doc.getRootElement(); |
503 | - /* alle Projekte durchlaufen */ |
504 | - List<Element> projekte = root.getChildren(); |
505 | - for (Iterator<Element> iter = projekte.iterator(); iter.hasNext();) { |
506 | - Element projekt = (Element) iter.next(); |
507 | - List<Element> projektnamen = projekt.getChildren("name"); |
508 | - for (Iterator<Element> iterator = projektnamen.iterator(); iterator.hasNext();) { |
509 | - Element projektname = (Element) iterator.next(); |
510 | - |
511 | - /* |
512 | - * wenn der Projektname aufgeführt wird, dann alle Digitalen Collectionen in die Liste |
513 | - */ |
514 | - if (projektname.getText().equalsIgnoreCase(prozessKopie.getProjekt().getTitel())) { |
515 | - List<Element> myCols = projekt.getChildren("DigitalCollection"); |
516 | - for (Iterator<Element> it2 = myCols.iterator(); it2.hasNext();) { |
517 | - Element col = (Element) it2.next(); |
518 | - possibleDigitalCollection.add(col.getText()); |
519 | - } |
520 | - } |
521 | - } |
522 | - } |
523 | + try{ |
524 | + possibleDigitalCollection = DigitalCollections.possibleDigitalCollectionsForProcess(prozessKopie); |
525 | + } catch (FileNotFoundException e1) { |
526 | + myLogger.error("File not found: ", e1); |
527 | + Helper.setFehlerMeldung("File not found: ", e1); |
528 | } catch (JDOMException e1) { |
529 | myLogger.error("error while parsing digital collections", e1); |
530 | Helper.setFehlerMeldung("Error while parsing digital collections", e1); |
531 | } catch (IOException e1) { |
532 | myLogger.error("error while parsing digital collections", e1); |
533 | Helper.setFehlerMeldung("Error while parsing digital collections", e1); |
534 | + } finally { |
535 | + if(possibleDigitalCollection == null) |
536 | + possibleDigitalCollection = new ArrayList<String>(); |
537 | } |
538 | |
539 | // if only one collection is possible take it directly |
540 | |
541 | === modified file 'src/de/sub/goobi/helper/Helper.java' |
542 | --- src/de/sub/goobi/helper/Helper.java 2012-07-03 13:32:53 +0000 |
543 | +++ src/de/sub/goobi/helper/Helper.java 2012-07-27 10:58:19 +0000 |
544 | @@ -42,13 +42,16 @@ |
545 | import javax.faces.context.FacesContext; |
546 | import javax.servlet.http.HttpServletRequest; |
547 | |
548 | +import org.apache.log4j.Level; |
549 | import org.apache.log4j.Logger; |
550 | +import org.goobi.mq.WebServiceResult; |
551 | import org.hibernate.Session; |
552 | import org.jdom.Element; |
553 | |
554 | import de.sub.goobi.beans.Benutzer; |
555 | import de.sub.goobi.config.ConfigMain; |
556 | import de.sub.goobi.forms.LoginForm; |
557 | +import de.sub.goobi.helper.enums.ReportLevel; |
558 | import de.sub.goobi.persistence.HibernateUtilOld; |
559 | |
560 | //TODO: Check if more method can be made static |
561 | @@ -59,6 +62,7 @@ |
562 | |
563 | private String myMetadatenVerzeichnis; |
564 | private String myConfigVerzeichnis; |
565 | + public static Map<String, String> activeMQReporting = null; |
566 | |
567 | /** |
568 | * Ermitteln eines bestimmten Paramters des Requests |
569 | @@ -145,43 +149,59 @@ |
570 | } |
571 | |
572 | /** |
573 | - * Dem aktuellen Formular eine Fehlermeldung für ein bestimmtes Control übergeben |
574 | + * The method setMeldung() adds an error message for a given control to the |
575 | + * current form. |
576 | + * |
577 | + * @param control |
578 | + * Name of control that caused the error or “null” if the error |
579 | + * was not caused by a control |
580 | + * @param messageKey |
581 | + * The key of the error message. The method will try to resolve |
582 | + * the key against its messages file. |
583 | + * @param descriptionKey |
584 | + * The description key of the error. The method will try to |
585 | + * resolve the key against its messages file. |
586 | + * @param infoOnly |
587 | + * Set to false for error messages. Set to true for info |
588 | + * messages. |
589 | */ |
590 | - private static void setMeldung(String control, String meldung, String beschreibung, boolean nurInfo) { |
591 | + private static void setMeldung(String control, String messageKey, |
592 | + String descriptionKey, boolean infoOnly) { |
593 | FacesContext context = FacesContext.getCurrentInstance(); |
594 | |
595 | - // Never forget: Strings are immutable |
596 | - meldung = meldung.replaceAll("<", "<"); |
597 | - meldung = meldung.replaceAll(">", ">"); |
598 | - beschreibung = beschreibung.replaceAll("<", "<"); |
599 | - beschreibung = beschreibung.replaceAll(">", ">"); |
600 | - /* wenn kein Kontext da ist, dann die Meldungen in Log */ |
601 | - if (context == null) { |
602 | - if (nurInfo) { |
603 | - myLogger.info(meldung + " " + beschreibung); |
604 | - } else { |
605 | - myLogger.error(meldung + " " + beschreibung); |
606 | - } |
607 | - return; |
608 | - } |
609 | - |
610 | - String msg = ""; |
611 | - String beschr = ""; |
612 | - try { |
613 | - msg = Messages.getString(meldung); |
614 | - } catch (RuntimeException e) { |
615 | - msg = meldung; |
616 | - } |
617 | - try { |
618 | - beschr = Messages.getString(beschreibung); |
619 | - } catch (RuntimeException e) { |
620 | - beschr = beschreibung; |
621 | - } |
622 | - |
623 | - if (nurInfo) { |
624 | - context.addMessage(control, new FacesMessage(FacesMessage.SEVERITY_INFO, msg, beschr)); |
625 | - } else { |
626 | - context.addMessage(control, new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, beschr)); |
627 | + String message; |
628 | + String description; |
629 | + try { |
630 | + message = Messages.getString(messageKey); |
631 | + } catch (RuntimeException e) { |
632 | + message = messageKey; |
633 | + } |
634 | + try { |
635 | + description = Messages.getString(descriptionKey); |
636 | + } catch (RuntimeException e) { |
637 | + description = descriptionKey; |
638 | + } |
639 | + |
640 | + message = message.replaceAll("<", "<").replaceAll(">", ">"); |
641 | + description = description.replaceAll("<", "<").replaceAll(">", ">"); |
642 | + |
643 | + String compoundMessage = message.replaceFirst(":\\s*$", "") + ": " |
644 | + + description; |
645 | + |
646 | + /* If the Active MQ service is at work, report errors there, too. */ |
647 | + if (activeMQReporting != null) { |
648 | + new WebServiceResult(activeMQReporting.get("queueName"), |
649 | + activeMQReporting.get("id"), infoOnly ? ReportLevel.INFO |
650 | + : ReportLevel.ERROR, compoundMessage).send(); |
651 | + } |
652 | + |
653 | + if (context != null) { |
654 | + context.addMessage( |
655 | + control, |
656 | + new FacesMessage(infoOnly ? FacesMessage.SEVERITY_INFO |
657 | + : FacesMessage.SEVERITY_ERROR, message, description)); |
658 | + } else { // wenn kein Kontext da ist, dann die Meldungen in Log |
659 | + myLogger.log(infoOnly ? Level.INFO : Level.ERROR, compoundMessage); |
660 | } |
661 | } |
662 | |
663 | |
664 | === modified file 'src/de/sub/goobi/helper/UghHelper.java' |
665 | --- src/de/sub/goobi/helper/UghHelper.java 2012-02-22 07:43:02 +0000 |
666 | +++ src/de/sub/goobi/helper/UghHelper.java 2012-07-27 10:58:19 +0000 |
667 | @@ -42,6 +42,7 @@ |
668 | import ugh.exceptions.DocStructHasNoTypeException; |
669 | import ugh.exceptions.MetadataTypeNotAllowedException; |
670 | import de.sub.goobi.beans.Prozess; |
671 | +import de.sub.goobi.config.ConfigMain; |
672 | import de.sub.goobi.helper.exceptions.UghHelperException; |
673 | |
674 | //TODO: Try to move this methods to UGH (ugh.util.UGHUtils would be a better place) |
675 | @@ -206,8 +207,13 @@ |
676 | public String convertLanguage(String inLanguage) { |
677 | /* Pfad zur Datei ermitteln */ |
678 | FacesContext context = FacesContext.getCurrentInstance(); |
679 | - HttpSession session = (HttpSession) context.getExternalContext().getSession(false); |
680 | - String filename = session.getServletContext().getRealPath("/WEB-INF") + File.separator + "classes" + File.separator + "opaclanguages.txt"; |
681 | + String filename; |
682 | + if (context != null) { |
683 | + HttpSession session = (HttpSession) context.getExternalContext().getSession(false); |
684 | + filename = session.getServletContext().getRealPath("/WEB-INF") + File.separator + "classes" + File.separator + "opaclanguages.txt"; |
685 | + } else { |
686 | + filename = ConfigMain.getParameter("KonfigurationVerzeichnis") + "opaclanguages.txt"; |
687 | + } |
688 | /* Datei zeilenweise durchlaufen und die Sprache vergleichen */ |
689 | try { |
690 | FileInputStream fis = new FileInputStream(filename); |
691 | @@ -233,8 +239,13 @@ |
692 | String temp = inString; |
693 | /* Pfad zur Datei ermitteln */ |
694 | FacesContext context = FacesContext.getCurrentInstance(); |
695 | - HttpSession session = (HttpSession) context.getExternalContext().getSession(false); |
696 | - String filename = session.getServletContext().getRealPath("/WEB-INF") + File.separator + "classes" + File.separator + "opacumlaut.txt"; |
697 | + String filename; |
698 | + if (context != null) { |
699 | + HttpSession session = (HttpSession) context.getExternalContext().getSession(false); |
700 | + filename = session.getServletContext().getRealPath("/WEB-INF") + File.separator + "classes" + File.separator + "opacumlaut.txt"; |
701 | + } else { |
702 | + filename = ConfigMain.getParameter("KonfigurationVerzeichnis") + "opacumlaut.txt"; |
703 | + } |
704 | |
705 | /* Datei zeilenweise durchlaufen und die Sprache vergleichen */ |
706 | try { |
707 | |
708 | === modified file 'src/de/sub/goobi/helper/WebDav.java' |
709 | --- src/de/sub/goobi/helper/WebDav.java 2012-07-03 13:49:14 +0000 |
710 | +++ src/de/sub/goobi/helper/WebDav.java 2012-07-27 10:58:19 +0000 |
711 | @@ -111,7 +111,8 @@ |
712 | |
713 | public void UploadFromHome(Prozess myProzess) { |
714 | Benutzer aktuellerBenutzer = (Benutzer) Helper.getManagedBeanValue("#{LoginForm.myBenutzer}"); |
715 | - UploadFromHome(aktuellerBenutzer, myProzess); |
716 | + if (aktuellerBenutzer != null) |
717 | + UploadFromHome(aktuellerBenutzer, myProzess); |
718 | } |
719 | |
720 | public void UploadFromHome(Benutzer inBenutzer, Prozess myProzess) { |
721 | |
722 | === added file 'src/de/sub/goobi/helper/enums/ReportLevel.java' |
723 | --- src/de/sub/goobi/helper/enums/ReportLevel.java 1970-01-01 00:00:00 +0000 |
724 | +++ src/de/sub/goobi/helper/enums/ReportLevel.java 2012-07-27 10:58:19 +0000 |
725 | @@ -0,0 +1,36 @@ |
726 | +/* |
727 | + * This file is part of the Goobi Application - a Workflow tool for the support of |
728 | + * mass digitization. |
729 | + * |
730 | + * Visit the websites for more information. |
731 | + * - http://gdz.sub.uni-goettingen.de |
732 | + * - http://www.goobi.org |
733 | + * - http://launchpad.net/goobi-production |
734 | + * |
735 | + * This program is free software; you can redistribute it and/or modify it under |
736 | + * the terms of the GNU General Public License as published by the Free Software |
737 | + * Foundation; either version 2 of the License, or (at your option) any later |
738 | + * version. |
739 | + * |
740 | + * This program is distributed in the hope that it will be useful, but WITHOUT ANY |
741 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
742 | + * PARTICULAR PURPOSE. See the GNU General Public License for more details. You |
743 | + * should have received a copy of the GNU General Public License along with this |
744 | + * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, |
745 | + * Suite 330, Boston, MA 02111-1307 USA |
746 | + */ |
747 | + |
748 | +package de.sub.goobi.helper.enums; |
749 | + |
750 | +/** |
751 | + * These are the possible states for output to “activeMQ.results.topic”. |
752 | + * |
753 | + * @author Matthias Ronge <matthias.ronge@zeutschel.de> |
754 | + */ |
755 | +public enum ReportLevel { |
756 | + FATAL, ERROR, WARN, INFO, SUCCESS, DEBUG, VERBOSE, LUDICROUS; |
757 | + |
758 | + public String toLowerCase() { |
759 | + return name().toLowerCase(); |
760 | + } |
761 | +} |
762 | |
763 | === added directory 'src/org/goobi/mq' |
764 | === added file 'src/org/goobi/mq/ActiveMQDirector.java' |
765 | --- src/org/goobi/mq/ActiveMQDirector.java 1970-01-01 00:00:00 +0000 |
766 | +++ src/org/goobi/mq/ActiveMQDirector.java 2012-07-27 10:58:19 +0000 |
767 | @@ -0,0 +1,231 @@ |
768 | +/* |
769 | + * This file is part of the Goobi Application - a Workflow tool for the support of |
770 | + * mass digitization. |
771 | + * |
772 | + * Visit the websites for more information. |
773 | + * - http://gdz.sub.uni-goettingen.de |
774 | + * - http://www.goobi.org |
775 | + * - http://launchpad.net/goobi-production |
776 | + * |
777 | + * This program is free software; you can redistribute it and/or modify it under |
778 | + * the terms of the GNU General Public License as published by the Free Software |
779 | + * Foundation; either version 2 of the License, or (at your option) any later |
780 | + * version. |
781 | + * |
782 | + * This program is distributed in the hope that it will be useful, but WITHOUT ANY |
783 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
784 | + * PARTICULAR PURPOSE. See the GNU General Public License for more details. You |
785 | + * should have received a copy of the GNU General Public License along with this |
786 | + * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, |
787 | + * Suite 330, Boston, MA 02111-1307 USA |
788 | + */ |
789 | + |
790 | +package org.goobi.mq; |
791 | + |
792 | +import javax.jms.Connection; |
793 | +import javax.jms.DeliveryMode; |
794 | +import javax.jms.Destination; |
795 | +import javax.jms.ExceptionListener; |
796 | +import javax.jms.JMSException; |
797 | +import javax.jms.MessageConsumer; |
798 | +import javax.jms.MessageProducer; |
799 | +import javax.jms.Session; |
800 | +import javax.servlet.ServletContextEvent; |
801 | +import javax.servlet.ServletContextListener; |
802 | + |
803 | +import org.apache.activemq.ActiveMQConnectionFactory; |
804 | +import org.apache.log4j.Logger; |
805 | +import org.goobi.mq.processors.CreateNewProcessProcessor; |
806 | +import org.goobi.mq.processors.FinaliseStepProcessor; |
807 | + |
808 | +import de.sub.goobi.config.ConfigMain; |
809 | + |
810 | +/** |
811 | + * The class ActiveMQDirector is the head of all Active MQ processors. It |
812 | + * implements the ServletContextListener interface and is − if configured in |
813 | + * web.xml − called automatically upon server starup. Its job is to connect to |
814 | + * the Active MQ server and register the listeners configured. |
815 | + * |
816 | + * The ActiveMQDirector should ALWAYS be declared in web.xml. The Active MQ |
817 | + * services are intended to be run in case that “activeMQ.hostURL” is configured |
818 | + * in the GoobiConfig.properties file. To disable the service, the entry there |
819 | + * should be emptied or commented out. Otherwise, a valid configuration would |
820 | + * not start working and an administrator will hardly have a chance to find out |
821 | + * why without inspecting the source code. |
822 | + * |
823 | + * The class ActiveMQDirector also provides a basic ExceptionListener |
824 | + * implementation as required for the connection. |
825 | + * |
826 | + * @author Matthias Ronge <matthias.ronge@zeutschel.de> |
827 | + */ |
828 | +public class ActiveMQDirector implements ServletContextListener, |
829 | + ExceptionListener { |
830 | + private static final Logger logger = Logger |
831 | + .getLogger(ActiveMQDirector.class); |
832 | + |
833 | + // *** CONFIGURATION *** |
834 | + // When implementing new Services, add them to this list: |
835 | + |
836 | + protected static ActiveMQProcessor[] services; |
837 | + static{ |
838 | + services = new ActiveMQProcessor[] { |
839 | + new CreateNewProcessProcessor(), |
840 | + new FinaliseStepProcessor() |
841 | + }; |
842 | + } |
843 | + |
844 | + protected static Connection connection = null; |
845 | + protected static Session session = null; |
846 | + protected static MessageProducer resultsTopic; |
847 | + |
848 | + /** |
849 | + * The method contextInitialized() is called by the web container on startup |
850 | + * and is used to start up the active MQ connection. All processors from |
851 | + * services[] are registered. |
852 | + * |
853 | + * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet |
854 | + * .ServletContextEvent) |
855 | + */ |
856 | + @Override |
857 | + public void contextInitialized(ServletContextEvent initialisation) { |
858 | + String activeMQHost = ConfigMain.getParameter("activeMQ.hostURL", null); |
859 | + if (activeMQHost != null) { |
860 | + session = connectToServer(activeMQHost); |
861 | + if (session != null) { |
862 | + registerListeners(services); |
863 | + if (ConfigMain.getParameter("activeMQ.results.topic", null) != null) { |
864 | + resultsTopic = setUpReportChannel(ConfigMain.getParameter("activeMQ.results.topic")); |
865 | + } } } } |
866 | + |
867 | + /** |
868 | + * Sets up a connection to an active MQ server. The connection object is |
869 | + * global because it is needed later to shut down the connection. |
870 | + * |
871 | + * @param server |
872 | + * should be “tcp://{host}:{port}” or “vm://localhost” in case |
873 | + * that the server is run inside the same virtual machine |
874 | + * @return the session object or “null” upon error |
875 | + */ |
876 | + protected Session connectToServer(String server) { |
877 | + try { |
878 | + connection = new ActiveMQConnectionFactory(server).createConnection(); |
879 | + connection.start(); |
880 | + connection.setExceptionListener(this); // → ActiveMQDirector.onException() |
881 | + return connection.createSession(false, Session.AUTO_ACKNOWLEDGE); |
882 | + } catch (Exception e) { |
883 | + logger.fatal("Error connecting to ActiveMQ server, giving up.", e); |
884 | + } |
885 | + return null; |
886 | + } |
887 | + |
888 | + /** |
889 | + * This method registers the listeners with the active MQ server. |
890 | + * |
891 | + * If a queue name was configured for a service, a MessageConsumer is set up |
892 | + * to listen on that queue and, in case of incoming messages, make the |
893 | + * service process the message. The message checker is saved inside the |
894 | + * service to be able to shut it down later. |
895 | + */ |
896 | + protected void registerListeners(ActiveMQProcessor[] processors) { |
897 | + for (ActiveMQProcessor processor : processors) { |
898 | + if (processor.getQueueName() != null) { |
899 | + MessageConsumer messageChecker = null; |
900 | + try { |
901 | + Destination queue = session.createQueue(processor.getQueueName()); |
902 | + messageChecker = session.createConsumer(queue); |
903 | + messageChecker.setMessageListener(processor); |
904 | + processor.saveChecker(messageChecker); |
905 | + } catch (Exception e) { |
906 | + logger.fatal("Error setting up monitoring for \"" + processor.getQueueName() + "\": Giving up.", e); |
907 | + } } } } |
908 | + |
909 | + /** |
910 | + * This sets up a connection to the topic the results shall be written to. |
911 | + * The delivery mode is set so “persistent” to allow consumers not online |
912 | + * with the server in the moment of message submission to read the messages |
913 | + * later. The log messages are set to be kept on the server for 7 days. This |
914 | + * value can be overridden from the GoobiConfig.properties parameter |
915 | + * “activeMQ.results.timeToLive”. The time to live must be specified in |
916 | + * milliseconds; 0 disables the oblivion. (See also: |
917 | + * http://docs.oracle.com/javaee/6/api/javax/jms/MessageProducer.html#setTimeToLive%28long%29 ) |
918 | + * |
919 | + * @param topic |
920 | + * name of the active MQ topic |
921 | + * @return a MessageProducer object ready for writing or “null” on error |
922 | + */ |
923 | + protected MessageProducer setUpReportChannel(String topic) { |
924 | + MessageProducer result; |
925 | + try { |
926 | + Destination channel = session.createTopic(topic); |
927 | + result = session.createProducer(channel); |
928 | + result.setDeliveryMode(DeliveryMode.PERSISTENT); |
929 | + result.setTimeToLive(ConfigMain.getLongParameter("activeMQ.results.timeToLive", 604800000)); |
930 | + return result; |
931 | + } catch (Exception e) { |
932 | + logger.fatal("Error setting up report channel \"" + topic + "\": Giving up.", e); |
933 | + } |
934 | + return null; |
935 | + } |
936 | + |
937 | + /** |
938 | + * This method is referenced from this.connectToServer() − see there. |
939 | + * |
940 | + * @see javax.jms.ExceptionListener#onException(javax.jms.JMSException) |
941 | + */ |
942 | + @Override |
943 | + public void onException(JMSException exce) { |
944 | + logger.error(exce); |
945 | + } |
946 | + |
947 | + /** |
948 | + * Any class that wants to create new Active MQ Messages needs read access |
949 | + * to the session, since Active MQ messages don’t have a constructor. |
950 | + * |
951 | + * @return the session object |
952 | + */ |
953 | + public static Session getSession() { |
954 | + return session; |
955 | + } |
956 | + |
957 | + /** |
958 | + * Instances of WebServiceResult can be sent by calling their send() method. |
959 | + * Therefore, they need read access on their topic. |
960 | + * |
961 | + * @return the resultsTopic object |
962 | + */ |
963 | + public static MessageProducer getResultsTopic() { |
964 | + return resultsTopic; |
965 | + } |
966 | + |
967 | + /** |
968 | + * The method contextDestroyed is called by the web container on shutdown. |
969 | + * It shuts down all listeners, the session and last, the connection. |
970 | + * |
971 | + * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet. |
972 | + * ServletContextEvent) |
973 | + */ |
974 | + @Override |
975 | + public void contextDestroyed(ServletContextEvent destruction) { |
976 | + // Shut down all watchers on any queues |
977 | + for (ActiveMQProcessor service : services) { |
978 | + MessageConsumer watcher = service.getChecker(); |
979 | + if (watcher != null) { |
980 | + try { |
981 | + watcher.close(); |
982 | + } catch (JMSException e) { |
983 | + logger.error(e); |
984 | + } } } |
985 | + |
986 | + // quit session |
987 | + try { |
988 | + session.close(); |
989 | + } catch (JMSException e) { |
990 | + logger.error(e); |
991 | + } |
992 | + |
993 | + // shut down connection |
994 | + try { |
995 | + connection.close(); |
996 | + } catch (JMSException e) { |
997 | + logger.error(e); |
998 | +} } } |
999 | |
1000 | === added file 'src/org/goobi/mq/ActiveMQProcessor.java' |
1001 | --- src/org/goobi/mq/ActiveMQProcessor.java 1970-01-01 00:00:00 +0000 |
1002 | +++ src/org/goobi/mq/ActiveMQProcessor.java 2012-07-27 10:58:19 +0000 |
1003 | @@ -0,0 +1,161 @@ |
1004 | +/* |
1005 | + * This file is part of the Goobi Application - a Workflow tool for the support of |
1006 | + * mass digitization. |
1007 | + * |
1008 | + * Visit the websites for more information. |
1009 | + * - http://gdz.sub.uni-goettingen.de |
1010 | + * - http://www.goobi.org |
1011 | + * - http://launchpad.net/goobi-production |
1012 | + * |
1013 | + * This program is free software; you can redistribute it and/or modify it under |
1014 | + * the terms of the GNU General Public License as published by the Free Software |
1015 | + * Foundation; either version 2 of the License, or (at your option) any later |
1016 | + * version. |
1017 | + * |
1018 | + * This program is distributed in the hope that it will be useful, but WITHOUT ANY |
1019 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
1020 | + * PARTICULAR PURPOSE. See the GNU General Public License for more details. You |
1021 | + * should have received a copy of the GNU General Public License along with this |
1022 | + * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, |
1023 | + * Suite 330, Boston, MA 02111-1307 USA |
1024 | + */ |
1025 | + |
1026 | +package org.goobi.mq; |
1027 | + |
1028 | +import java.util.HashMap; |
1029 | +import java.util.Map; |
1030 | + |
1031 | +import javax.jms.MapMessage; |
1032 | +import javax.jms.Message; |
1033 | +import javax.jms.MessageConsumer; |
1034 | +import javax.jms.MessageListener; |
1035 | + |
1036 | +import de.sub.goobi.helper.Helper; |
1037 | +import de.sub.goobi.helper.enums.ReportLevel; |
1038 | + |
1039 | +/** |
1040 | + * The class ActiveMQProcessor offers general services, such as making the |
1041 | + * incoming messages available as MapMessages and publishing the results. When I |
1042 | + * came clear that this code would be necessary for every processor, I thought |
1043 | + * an abstract class would be the right place for it. ActiveMQProcessor also |
1044 | + * provides a place to save the checker for the ActiveMQDirector, to be able to |
1045 | + * shut it down later. |
1046 | + * |
1047 | + * @author Matthias Ronge <matthias.ronge@zeutschel.de> |
1048 | + */ |
1049 | +public abstract class ActiveMQProcessor implements MessageListener { |
1050 | + |
1051 | + protected String queueName; // the queue name will be available here |
1052 | + private MessageConsumer checker; |
1053 | + |
1054 | + /** |
1055 | + * Implement the method process() to let your service actually do what you |
1056 | + * want him to do. |
1057 | + * |
1058 | + * @param ticket |
1059 | + * A MapMessage which can be processor-specific except that it |
1060 | + * requires to have a field “id”. |
1061 | + */ |
1062 | + protected abstract void process(MapMessageObjectReader ticket) throws Exception; |
1063 | + |
1064 | + /** |
1065 | + * Instantiating the class ActiveMQProcessor always requires to pass the |
1066 | + * name of the queue it should be attached to. That means, your constructor |
1067 | + * should look like this: |
1068 | + * |
1069 | + * <pre> |
1070 | + * public MyServiceProcessor(){ |
1071 | + * super(ConfigMain.getParameter("activeMQ.myService.queue", null)); |
1072 | + * } |
1073 | + * </pre> |
1074 | + * |
1075 | + * If the parameter is not set in GoobiConfig.properties, it will return |
1076 | + * “null” and so prevents it from being set up in ActiveMQDirector. |
1077 | + * |
1078 | + * @param queueName |
1079 | + * the queue name, if configured, or “null” to prevent the |
1080 | + * processor from being connected. |
1081 | + */ |
1082 | + public ActiveMQProcessor(String queueName) { |
1083 | + this.queueName = queueName; |
1084 | + } |
1085 | + |
1086 | + /** |
1087 | + * The method onMessage() provides a corset which checks the message being a |
1088 | + * MapMessage, performs a type safe type cast, and extracts the job’s ID to |
1089 | + * be able to send useful results to the results topic, which it does after |
1090 | + * the abstract method process() has finished. |
1091 | + * |
1092 | + * Since this will be the same for all processors which use MapMessages, I |
1093 | + * extracted the portion into the abstract class. |
1094 | + * |
1095 | + * @see javax.jms.MessageListener#onMessage(javax.jms.Message) |
1096 | + */ |
1097 | + @Override |
1098 | + public void onMessage(Message arg) { |
1099 | + MapMessageObjectReader ticket = null; |
1100 | + String ticketID = null; |
1101 | + |
1102 | + try { |
1103 | + // Basic check ticket |
1104 | + if (arg instanceof MapMessage) |
1105 | + ticket = new MapMessageObjectReader((MapMessage) arg); |
1106 | + else |
1107 | + throw new IllegalArgumentException("Incompatible types."); |
1108 | + ticketID = ticket.getMandatoryString("id"); |
1109 | + |
1110 | + // turn on logging |
1111 | + Map<String,String> loggingConfig = new HashMap<String,String>(); |
1112 | + loggingConfig.put("queueName", queueName); |
1113 | + loggingConfig.put("id", ticketID); |
1114 | + Helper.activeMQReporting = loggingConfig; |
1115 | + |
1116 | + // process ticket |
1117 | + process(ticket); |
1118 | + |
1119 | + // turn off logging again |
1120 | + Helper.activeMQReporting = null; |
1121 | + |
1122 | + // if everything ‘s fine, report success |
1123 | + new WebServiceResult(queueName, ticketID, ReportLevel.SUCCESS) |
1124 | + .send(); |
1125 | + |
1126 | + } catch (Exception exce) { |
1127 | + // report any errors |
1128 | + new WebServiceResult(queueName, ticketID, ReportLevel.FATAL, |
1129 | + exce.getMessage()).send(); |
1130 | + } |
1131 | + } |
1132 | + |
1133 | + /** |
1134 | + * This method is used to get the queue name upon initialisation. |
1135 | + * |
1136 | + * @return the queue name |
1137 | + */ |
1138 | + public String getQueueName() { |
1139 | + return queueName; |
1140 | + } |
1141 | + |
1142 | + /** |
1143 | + * The parent object which is there to check for new messages and to trigger |
1144 | + * the method onMessage() is saved inside the class, to have it lately for |
1145 | + * shutting down the service again. |
1146 | + * |
1147 | + * @param checker |
1148 | + * the MessageConsumer object responsible for checking messages |
1149 | + */ |
1150 | + |
1151 | + public void saveChecker(MessageConsumer checker) { |
1152 | + this.checker = checker; |
1153 | + } |
1154 | + |
1155 | + /** |
1156 | + * This method is used to get back the message checking object upon |
1157 | + * shutdown. |
1158 | + * |
1159 | + * @return the MessageConsumer object responsible for checking messages |
1160 | + */ |
1161 | + public MessageConsumer getChecker() { |
1162 | + return checker; |
1163 | + } |
1164 | +} |
1165 | |
1166 | === added file 'src/org/goobi/mq/MapMessageObjectReader.java' |
1167 | --- src/org/goobi/mq/MapMessageObjectReader.java 1970-01-01 00:00:00 +0000 |
1168 | +++ src/org/goobi/mq/MapMessageObjectReader.java 2012-07-27 10:58:19 +0000 |
1169 | @@ -0,0 +1,242 @@ |
1170 | +/* |
1171 | + * This file is part of the Goobi Application - a Workflow tool for the support of |
1172 | + * mass digitization. |
1173 | + * |
1174 | + * Visit the websites for more information. |
1175 | + * - http://gdz.sub.uni-goettingen.de |
1176 | + * - http://www.goobi.org |
1177 | + * - http://launchpad.net/goobi-production |
1178 | + * |
1179 | + * This program is free software; you can redistribute it and/or modify it under |
1180 | + * the terms of the GNU General Public License as published by the Free Software |
1181 | + * Foundation; either version 2 of the License, or (at your option) any later |
1182 | + * version. |
1183 | + * |
1184 | + * This program is distributed in the hope that it will be useful, but WITHOUT ANY |
1185 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
1186 | + * PARTICULAR PURPOSE. See the GNU General Public License for more details. You |
1187 | + * should have received a copy of the GNU General Public License along with this |
1188 | + * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, |
1189 | + * Suite 330, Boston, MA 02111-1307 USA |
1190 | + */ |
1191 | + |
1192 | +package org.goobi.mq; |
1193 | + |
1194 | +import java.util.Collection; |
1195 | +import java.util.HashMap; |
1196 | +import java.util.HashSet; |
1197 | +import java.util.Map; |
1198 | +import java.util.Set; |
1199 | + |
1200 | +import javax.jms.JMSException; |
1201 | +import javax.jms.MapMessage; |
1202 | + |
1203 | +public class MapMessageObjectReader { |
1204 | + |
1205 | + private MapMessage ticket; |
1206 | + |
1207 | + /** |
1208 | + * This instantiates a new MapMessageObjectReader which is attached to a |
1209 | + * given MapMessage. |
1210 | + * |
1211 | + * @param message |
1212 | + */ |
1213 | + public MapMessageObjectReader(MapMessage message) { |
1214 | + if (message == null) |
1215 | + throw new IllegalArgumentException( |
1216 | + "MapMessageObjectReader: null argument in constructor."); |
1217 | + this.ticket = message; |
1218 | + } |
1219 | + |
1220 | + /** |
1221 | + * The function getMandatorySetOfString() fetches a Set<String> from a MapMessage. |
1222 | + * This is a strict implementation that requires the collection not to be |
1223 | + * empty and not to contain the null element. |
1224 | + * |
1225 | + * Please note that the Set is allowed to contain the empty String (“”). |
1226 | + * |
1227 | + * @param key |
1228 | + * the name of the set to return |
1229 | + * @return the set requested |
1230 | + * @throws IllegalArgumentException |
1231 | + * in case that getObject returns null, the returned object is |
1232 | + * not of type Collection, any of the content elements are not |
1233 | + * of type String or the collection is empty at all. |
1234 | + * @throws JMSException |
1235 | + * can be thrown by MapMessage.getObject(String) |
1236 | + */ |
1237 | + public Set<String> getMandatorySetOfString(String key) |
1238 | + throws IllegalArgumentException, JMSException { |
1239 | + Set<String> result = new HashSet<String>(); |
1240 | + Boolean emptiness = Boolean.TRUE; |
1241 | + |
1242 | + Object collectionObject = ticket.getObject(key); |
1243 | + if (collectionObject == null) |
1244 | + throw new IllegalArgumentException("Missing mandatory argument: \"" |
1245 | + + key + "\""); |
1246 | + if (!(collectionObject instanceof Collection<?>)) |
1247 | + throw new IllegalArgumentException("Incompatible types: \"" + key |
1248 | + + "\" was not found to be of type Collection<?>."); |
1249 | + for (Object contentObject : (Collection<?>) collectionObject) { |
1250 | + if (contentObject == null || !(contentObject instanceof String)) |
1251 | + throw new IllegalArgumentException( |
1252 | + "Incompatible types: An element of \"" + key |
1253 | + + "\" was not found to be of type String."); |
1254 | + result.add((String) contentObject); |
1255 | + emptiness = false; |
1256 | + } |
1257 | + if (emptiness) |
1258 | + throw new IllegalArgumentException("Missing mandatory argument: \"" |
1259 | + + key + "\" must not be empty."); |
1260 | + return result; |
1261 | + } |
1262 | + |
1263 | + /** |
1264 | + * The function getMandatoryString() fetches a String from a MapMessage. This is |
1265 | + * a strict implementation that requires the string not to be null and not |
1266 | + * to be empty. |
1267 | + * |
1268 | + * @param key |
1269 | + * the name of the string to return |
1270 | + * @return the string requested |
1271 | + * @throws IllegalArgumentException |
1272 | + * in case that getObject returns null or the returned string is |
1273 | + * of length “0”. |
1274 | + * @throws JMSException |
1275 | + * can be thrown by MapMessage.getString(String) |
1276 | + */ |
1277 | + public String getMandatoryString(String key) throws IllegalArgumentException, |
1278 | + JMSException { |
1279 | + String result = ticket.getString(key); |
1280 | + if (result == null || result.length() == 0) |
1281 | + throw new IllegalArgumentException("Missing mandatory argument: \"" |
1282 | + + key + "\""); |
1283 | + return result; |
1284 | + } |
1285 | + |
1286 | + /** |
1287 | + * The function getString() fetches a String from a MapMessage. This is an |
1288 | + * access forward to the native function of the MapMessage. You may |
1289 | + * consider to use getMandatoryString() instead. |
1290 | + * |
1291 | + * @param key |
1292 | + * the name of the string to return |
1293 | + * @return the string requested (may be null or empty) |
1294 | + * @throws JMSException |
1295 | + * can be thrown by MapMessage.getString(String) |
1296 | + */ |
1297 | + |
1298 | + public String getString(String key) throws JMSException { |
1299 | + return ticket.getString(key); |
1300 | + } |
1301 | + |
1302 | + /** |
1303 | + * The function getMandatoryInteger() fetches an Integer object from a MapMessage. This is |
1304 | + * a strict implementation that requires the Integer not to be null. |
1305 | + * |
1306 | + * @param key |
1307 | + * the name of the string to return |
1308 | + * @return the string requested |
1309 | + * @throws IllegalArgumentException |
1310 | + * in case that getObject returns null |
1311 | + * @throws JMSException |
1312 | + * can be thrown by MapMessage.getString(String) |
1313 | + */ |
1314 | + public Integer getMandatoryInteger(String key) throws IllegalArgumentException, |
1315 | + JMSException { |
1316 | + Integer result = ticket.getInt(key); |
1317 | + if (result == null) |
1318 | + throw new IllegalArgumentException("Missing mandatory argument: \"" |
1319 | + + key + "\""); |
1320 | + return result; |
1321 | + } |
1322 | + |
1323 | + /** |
1324 | + * The function getMapOfStringToString() fetches a Map<String,String> from a |
1325 | + * MapMessage. This is a partly strict implementation that allows no null |
1326 | + * element neither as key, nor as value. However, if no object was found for |
1327 | + * the given key, or the key returned a null object, the function returns |
1328 | + * null. Also, the Map is allowed to contain the empty String (“”) as key |
1329 | + * and as values. |
1330 | + * |
1331 | + * @param key |
1332 | + * the name of the map to return |
1333 | + * @return the map requested |
1334 | + * @throws IllegalArgumentException |
1335 | + * in case that the object returned by getObject returned object |
1336 | + * is not of type Map or any of the content elements are not of |
1337 | + * type String. |
1338 | + */ |
1339 | + public Map<String, String> getMapOfStringToString(String key) { |
1340 | + Map<String, String> result = new HashMap<String, String>(); |
1341 | + |
1342 | + Object mapObject = null; |
1343 | + try { |
1344 | + mapObject = ticket.getObject(key); |
1345 | + } catch (Exception irrelevant) { |
1346 | + } |
1347 | + if (mapObject == null) |
1348 | + return null; |
1349 | + |
1350 | + if (!(mapObject instanceof Map<?, ?>)) |
1351 | + throw new IllegalArgumentException("Incompatible types: \"" + key |
1352 | + + "\" was not found to be of type Map<?, ?>."); |
1353 | + for (Object keyObject : ((Map<?, ?>) mapObject).keySet()) { |
1354 | + Object valueObject = ((Map<?, ?>) mapObject).get(keyObject); |
1355 | + if (keyObject == null || !(keyObject instanceof String)) |
1356 | + throw new IllegalArgumentException( |
1357 | + "Incompatible types: A key element of \"" + key |
1358 | + + "\" was not found to be of type String."); |
1359 | + if (valueObject == null || !(valueObject instanceof String)) |
1360 | + throw new IllegalArgumentException( |
1361 | + "Incompatible types: A value element of \"" + key |
1362 | + + "\" was not found to be of type String."); |
1363 | + result.put((String) keyObject, (String) valueObject); |
1364 | + } |
1365 | + |
1366 | + return result; |
1367 | + } |
1368 | + |
1369 | + /** |
1370 | + * The function hasField() tests whether a field can be obtained from a |
1371 | + * MapMessage. |
1372 | + * |
1373 | + * @param string |
1374 | + * name of the field |
1375 | + * @return true or false |
1376 | + * @throws IllegalArgumentException |
1377 | + * can be thrown by MapMessage |
1378 | + * @throws JMSException |
1379 | + * can be thrown by MapMessage |
1380 | + */ |
1381 | + public boolean hasField(String string) throws IllegalArgumentException, |
1382 | + JMSException { |
1383 | + String result = ticket.getString(string); |
1384 | + return (result != null && result.length() > 0); |
1385 | + } |
1386 | +} |
1387 | + |
1388 | + |
1389 | + |
1390 | + |
1391 | + |
1392 | + |
1393 | + |
1394 | + |
1395 | + |
1396 | + |
1397 | + |
1398 | + |
1399 | + |
1400 | + |
1401 | + |
1402 | + |
1403 | + |
1404 | + |
1405 | + |
1406 | + |
1407 | + |
1408 | + |
1409 | + |
1410 | + |
1411 | + |
1412 | |
1413 | === added file 'src/org/goobi/mq/WebServiceResult.java' |
1414 | --- src/org/goobi/mq/WebServiceResult.java 1970-01-01 00:00:00 +0000 |
1415 | +++ src/org/goobi/mq/WebServiceResult.java 2012-07-27 10:58:19 +0000 |
1416 | @@ -0,0 +1,88 @@ |
1417 | +/* |
1418 | + * This file is part of the Goobi Application - a Workflow tool for the support of |
1419 | + * mass digitization. |
1420 | + * |
1421 | + * Visit the websites for more information. |
1422 | + * - http://gdz.sub.uni-goettingen.de |
1423 | + * - http://www.goobi.org |
1424 | + * - http://launchpad.net/goobi-production |
1425 | + * |
1426 | + * This program is free software; you can redistribute it and/or modify it under |
1427 | + * the terms of the GNU General Public License as published by the Free Software |
1428 | + * Foundation; either version 2 of the License, or (at your option) any later |
1429 | + * version. |
1430 | + * |
1431 | + * This program is distributed in the hope that it will be useful, but WITHOUT ANY |
1432 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
1433 | + * PARTICULAR PURPOSE. See the GNU General Public License for more details. You |
1434 | + * should have received a copy of the GNU General Public License along with this |
1435 | + * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, |
1436 | + * Suite 330, Boston, MA 02111-1307 USA |
1437 | + */ |
1438 | + |
1439 | +package org.goobi.mq; |
1440 | + |
1441 | +import javax.jms.MapMessage; |
1442 | + |
1443 | +import org.apache.log4j.Level; |
1444 | +import org.apache.log4j.Logger; |
1445 | +import org.joda.time.DateTime; |
1446 | +import org.joda.time.format.DateTimeFormatter; |
1447 | +import org.joda.time.format.ISODateTimeFormat; |
1448 | + |
1449 | +import de.sub.goobi.helper.enums.ReportLevel; |
1450 | + |
1451 | +public class WebServiceResult { |
1452 | + private static final Logger logger = Logger.getLogger(ActiveMQDirector.class); |
1453 | + |
1454 | + private String queueName; |
1455 | + private String id; |
1456 | + private ReportLevel level; |
1457 | + private String message = null; |
1458 | + |
1459 | + public WebServiceResult(String queueName, String id, ReportLevel level, |
1460 | + String message){ |
1461 | + this.queueName = queueName; |
1462 | + this.id = id; |
1463 | + this.level = level; |
1464 | + this.message = message; |
1465 | + } |
1466 | + |
1467 | + public WebServiceResult(String queueName, String id, ReportLevel level){ |
1468 | + this.queueName = queueName; |
1469 | + this.id = id; |
1470 | + this.level = level; |
1471 | + } |
1472 | + |
1473 | + public void send() { |
1474 | + if (ActiveMQDirector.getResultsTopic() == null) { |
1475 | + |
1476 | + // If reporting to ActiveMQ is disabled, write log message |
1477 | + logger.log(level == ReportLevel.SUCCESS ? Level.INFO : Level.WARN, |
1478 | + "Processing message \"" + id + '@' + queueName |
1479 | + + "\" reports " + level.toLowerCase() + "." |
1480 | + + (message != null ? " (" + message + ")" : "")); |
1481 | + } else { |
1482 | + try { |
1483 | + MapMessage report = ActiveMQDirector.getSession().createMapMessage(); |
1484 | + |
1485 | + DateTime now = new DateTime(); |
1486 | + DateTimeFormatter iso8601formatter = ISODateTimeFormat.dateTime(); |
1487 | + report.setString("timestamp", iso8601formatter.print(now)); |
1488 | + report.setString("queue", queueName); |
1489 | + report.setString("id", id); |
1490 | + report.setString("level", level.toLowerCase()); |
1491 | + if (message != null) |
1492 | + report.setString("message", message); |
1493 | + |
1494 | + ActiveMQDirector.getResultsTopic().send(report); |
1495 | + |
1496 | + } catch (Exception exce) { |
1497 | + logger.fatal("Error sending report for \"" + id + '@' |
1498 | + + queueName + "\" (" + level.toLowerCase() |
1499 | + + (message != null ? ": " + message : "") |
1500 | + + "): Giving up.", exce); |
1501 | + } |
1502 | + } |
1503 | + } |
1504 | +} |
1505 | |
1506 | === added directory 'src/org/goobi/mq/processors' |
1507 | === added file 'src/org/goobi/mq/processors/CreateNewProcessProcessor.java' |
1508 | --- src/org/goobi/mq/processors/CreateNewProcessProcessor.java 1970-01-01 00:00:00 +0000 |
1509 | +++ src/org/goobi/mq/processors/CreateNewProcessProcessor.java 2012-07-27 10:58:19 +0000 |
1510 | @@ -0,0 +1,375 @@ |
1511 | +/* |
1512 | + * This file is part of the Goobi Application - a Workflow tool for the support of |
1513 | + * mass digitization. |
1514 | + * |
1515 | + * Visit the websites for more information. |
1516 | + * - http://gdz.sub.uni-goettingen.de |
1517 | + * - http://www.goobi.org |
1518 | + * - http://launchpad.net/goobi-production |
1519 | + * |
1520 | + * This program is free software; you can redistribute it and/or modify it under |
1521 | + * the terms of the GNU General Public License as published by the Free Software |
1522 | + * Foundation; either version 2 of the License, or (at your option) any later |
1523 | + * version. |
1524 | + * |
1525 | + * This program is distributed in the hope that it will be useful, but WITHOUT ANY |
1526 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
1527 | + * PARTICULAR PURPOSE. See the GNU General Public License for more details. You |
1528 | + * should have received a copy of the GNU General Public License along with this |
1529 | + * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, |
1530 | + * Suite 330, Boston, MA 02111-1307 USA |
1531 | + */ |
1532 | + |
1533 | +package org.goobi.mq.processors; |
1534 | + |
1535 | +import java.util.ArrayList; |
1536 | +import java.util.HashSet; |
1537 | +import java.util.Iterator; |
1538 | +import java.util.List; |
1539 | +import java.util.Map; |
1540 | +import java.util.Set; |
1541 | + |
1542 | +import org.apache.log4j.Logger; |
1543 | +import org.goobi.mq.ActiveMQProcessor; |
1544 | +import org.goobi.mq.MapMessageObjectReader; |
1545 | +import org.hibernate.Criteria; |
1546 | +import org.hibernate.Session; |
1547 | + |
1548 | +import de.sub.goobi.beans.Prozess; |
1549 | +import de.sub.goobi.config.ConfigMain; |
1550 | +import de.sub.goobi.config.ConfigOpacDoctype; |
1551 | +import de.sub.goobi.forms.AdditionalField; |
1552 | +import de.sub.goobi.forms.ProzesskopieForm; |
1553 | +import de.sub.goobi.helper.Helper; |
1554 | + |
1555 | +/** |
1556 | + * CreateNewProcessProcessor is an Apache Active MQ consumer which registers to |
1557 | + * a queue configured by "activeMQ.createNewProcess.queue" on application |
1558 | + * startup. It was designed to create new processes from outside Goobi. There |
1559 | + * are two ways providing to create new processes. If the MapMessage on that |
1560 | + * queue contains of all the fields listed, the bibliographic data is retrieved |
1561 | + * using a catalogue configured within Goobi. If “opac” is missing, it will try |
1562 | + * to create a process just upon the data passed in the “userFields” − “field” |
1563 | + * and “value” will be ignored in that case, and the “docType” can be set |
1564 | + * manually. |
1565 | + * |
1566 | + * Field summary: |
1567 | + * |
1568 | + * <dl> |
1569 | + * <dt>String template</dt> |
1570 | + * <dd>name of the process template to use.</dd> |
1571 | + * <dt>String opac</dt> |
1572 | + * <dd>Cataloge to use for lookup.</dd> |
1573 | + * <dt>String field</dt> |
1574 | + * <dd>Field to look into, usually 12 (PPN).</dd> |
1575 | + * <dt>String value</dt> |
1576 | + * <dd>Value to look for, id of physical medium</dd> |
1577 | + * <dt>String docType</dt> |
1578 | + * <dd>DocType value to use if no catalogue request is performed.</dd> |
1579 | + * <dt>Set<String> collections</dt> |
1580 | + * <dd>Collections to be selected.</dd> |
1581 | + * <dt>Map<String, String> userFields collections</dt> |
1582 | + * <dd>Fields to be populated manually. |
1583 | + * </dd> |
1584 | + * </dl> |
1585 | + * |
1586 | + * @author Matthias Ronge <matthias.ronge@zeutschel.de> |
1587 | + */ |
1588 | +public class CreateNewProcessProcessor extends ActiveMQProcessor { |
1589 | + private static final Logger logger = Logger.getLogger(CreateNewProcessProcessor.class); |
1590 | + |
1591 | + public CreateNewProcessProcessor() { |
1592 | + super(ConfigMain.getParameter("activeMQ.createNewProcess.queue", null)); |
1593 | + } |
1594 | + |
1595 | + @Override |
1596 | + protected void process(MapMessageObjectReader args) throws Exception { |
1597 | + |
1598 | + Set<String> collections = args.getMandatorySetOfString("collections"); |
1599 | + String id = args.getMandatoryString("id"); |
1600 | + String template = args.getMandatoryString("template"); |
1601 | + Map<String, String> userFields = args.getMapOfStringToString("userFields"); |
1602 | + if (args.hasField("opac")) { |
1603 | + String opac = args.getMandatoryString("opac"); |
1604 | + String field = args.getMandatoryString("field"); |
1605 | + String value = args.getMandatoryString("value"); |
1606 | + createNewProcessMain(template, opac, field, value, id, null, collections, userFields); |
1607 | + } else { |
1608 | + String docType = args.getString("docType"); |
1609 | + createNewProcessMain(template, null, null, null, id, docType, collections, userFields); |
1610 | + } |
1611 | + |
1612 | + } |
1613 | + |
1614 | + /** |
1615 | + * This is the main routine used to create new processes. |
1616 | + * |
1617 | + * @param template |
1618 | + * titel of the process template the new process shall be derived |
1619 | + * from |
1620 | + * @param opac |
1621 | + * name of the connection to a library catalogue to load the |
1622 | + * bibliographic data from (may be null) |
1623 | + * @param field |
1624 | + * number of the catalogue search field (ignored if “opac” is |
1625 | + * null) |
1626 | + * @param value |
1627 | + * search string (ignored if “opac” is null) |
1628 | + * @param id |
1629 | + * identifier to be used for the digitisation |
1630 | + * @param docType |
1631 | + * docType to set (may be null) |
1632 | + * @param collections |
1633 | + * collections to add the digitisation to |
1634 | + * @param userFields |
1635 | + * Values for additional fields can be set here (may be null) |
1636 | + * @throws Exception |
1637 | + * in various cases, such as bad parameters or errors in the |
1638 | + * underlying layers |
1639 | + */ |
1640 | + protected void createNewProcessMain(String template, String opac, String field, String value, String id, String docType, |
1641 | + Set<String> collections, Map<String, String> userFields) throws Exception { |
1642 | + |
1643 | + try { |
1644 | + ProzesskopieForm newProcess = newProcessFromTemplate(template); |
1645 | + newProcess.setDigitalCollections(validCollectionsForProcess(collections, newProcess)); |
1646 | + if (opac != null) |
1647 | + getBibliorgaphicData(newProcess, opac, field, value); |
1648 | + if (docType != null && docTypeIsPossible(newProcess, docType)) |
1649 | + newProcess.setDocType(docType); |
1650 | + if (userFields != null) |
1651 | + setUserFields(newProcess, userFields); |
1652 | + newProcess.CalcProzesstitel(); |
1653 | + String state = newProcess.NeuenProzessAnlegen(); |
1654 | + if (!state.equals("ProzessverwaltungKopie3")) |
1655 | + throw new RuntimeException(); |
1656 | + logger.info("Created new process: " + id); |
1657 | + } catch (Exception exited) { |
1658 | + logger.error("Failed to create new process: " + id, exited); |
1659 | + throw exited; |
1660 | + } |
1661 | + } |
1662 | + |
1663 | + /** |
1664 | + * The function newProcessFromTemplate() derives a ProzesskopieForm object |
1665 | + * from a given template. |
1666 | + * |
1667 | + * @param templateTitle |
1668 | + * titel value of the template to look for |
1669 | + * @return a ProzesskopieForm object, prepared from a given template |
1670 | + * @throws IllegalArgumentException |
1671 | + * if no suitable template is found |
1672 | + */ |
1673 | + protected ProzesskopieForm newProcessFromTemplate(String templateTitle) throws IllegalArgumentException { |
1674 | + ProzesskopieForm result = new ProzesskopieForm(); |
1675 | + |
1676 | + List<Prozess> allTemplates = allTemplatesFromDatabase(); |
1677 | + Prozess selectedTemplate = selectTemplateByTitle(allTemplates, templateTitle); |
1678 | + result.setProzessVorlage(selectedTemplate); |
1679 | + result.Prepare(); |
1680 | + return result; |
1681 | + } |
1682 | + |
1683 | + /** |
1684 | + * This method reads all Prozess objects from the hibernate. |
1685 | + * |
1686 | + * @return a List<Prozess> holding all templates |
1687 | + */ |
1688 | + protected List<Prozess> allTemplatesFromDatabase() { |
1689 | + Session hibernateSession = Helper.getHibernateSession(); |
1690 | + Criteria request = hibernateSession.createCriteria(Prozess.class); |
1691 | + |
1692 | + @SuppressWarnings("unchecked") |
1693 | + List<Prozess> result = (List<Prozess>) request.list(); |
1694 | + |
1695 | + return result; |
1696 | + } |
1697 | + |
1698 | + /** |
1699 | + * The function selectTemplateByTitle() iterates over a List of Prozess and |
1700 | + * returns the first element whose titel equals the given templateTitle. |
1701 | + * |
1702 | + * @param allTemplates |
1703 | + * a List<Prozess> which shall be examined |
1704 | + * @param templateTitle |
1705 | + * the title of the template to be picked up |
1706 | + * @return the template, if found |
1707 | + * @throws IllegalArgumentException |
1708 | + * is thrown, if there is no template matching the given |
1709 | + * templateTitle |
1710 | + */ |
1711 | + protected Prozess selectTemplateByTitle(List<Prozess> allTemplates, String templateTitle) throws IllegalArgumentException { |
1712 | + |
1713 | + Prozess result = null; |
1714 | + for (Prozess aTemplate : allTemplates) { |
1715 | + if (aTemplate.getTitel().equals(templateTitle)) { |
1716 | + result = aTemplate; |
1717 | + break; |
1718 | + } |
1719 | + } |
1720 | + if (result == null) |
1721 | + throw new IllegalArgumentException("Bad argument: No template \"" + templateTitle + "\" available."); |
1722 | + return result; |
1723 | + } |
1724 | + |
1725 | + /** |
1726 | + * The function validCollectionsForProcess() tests whether a given set of |
1727 | + * collections can be assigned to new process. If so, the set of collections |
1728 | + * is returned as a list ready for assignment. |
1729 | + * |
1730 | + * @param collections |
1731 | + * a set of collection names to be tested |
1732 | + * @param process |
1733 | + * a ProzesskopieForm object whose prozessVorlage has been set |
1734 | + * @return an ArrayList which can be used to set the digitalCollections of a |
1735 | + * ProzesskopieForm |
1736 | + * @throws IllegalArgumentException |
1737 | + * in case that the given collection isn’t a valid subset of the |
1738 | + * digitalCollections possible here |
1739 | + */ |
1740 | + protected List<String> validCollectionsForProcess(Set<String> collections, ProzesskopieForm process) throws IllegalArgumentException { |
1741 | + |
1742 | + HashSet<String> possibleCollections = new HashSet<String>(process.getPossibleDigitalCollections()); |
1743 | + if (!possibleCollections.containsAll(collections)) |
1744 | + throw new IllegalArgumentException("Bad argument: One or more elements of \"collections\" is not available for template \"" |
1745 | + + process.getProzessVorlage().getTitel() + "\"."); |
1746 | + return new ArrayList<String>(collections); |
1747 | + } |
1748 | + |
1749 | + /** |
1750 | + * The function docTypeIsPossible() tests whether a given docType String can |
1751 | + * be applied to a given process template. If so, it will return “true”, |
1752 | + * otherwise, it will throw an informative IllegalArgumentException. |
1753 | + * |
1754 | + * @param dialog |
1755 | + * the ProzesskopieForm object to test against |
1756 | + * @param docType |
1757 | + * the desired docType ID string |
1758 | + * @return true on success |
1759 | + * @throws IllegalArgumentException |
1760 | + * if a docType is not applicable to the template or the docType |
1761 | + * isn’t valid |
1762 | + */ |
1763 | + protected boolean docTypeIsPossible(ProzesskopieForm dialog, String docType) throws IllegalArgumentException { |
1764 | + Boolean fieldIsUsed = dialog.getStandardFields().get("doctype"); |
1765 | + if (fieldIsUsed == null || fieldIsUsed.equals(Boolean.FALSE)) |
1766 | + throw new IllegalArgumentException("Bad argument “docType”: Selected template doesn’t provide the standard field “doctype”."); |
1767 | + |
1768 | + boolean valueIsValid = false; |
1769 | + Iterator<ConfigOpacDoctype> configOpacDoctypeIterator = dialog.getAllDoctypes().iterator(); |
1770 | + do { |
1771 | + ConfigOpacDoctype option = configOpacDoctypeIterator.next(); |
1772 | + valueIsValid = docType.equals(option.getTitle()); |
1773 | + } while (!valueIsValid && configOpacDoctypeIterator.hasNext()); |
1774 | + if (valueIsValid) |
1775 | + return true; |
1776 | + throw new IllegalArgumentException("Bad argument “docType”: Selected template doesn’t provide a docType “{0}”.".replace("{0}", |
1777 | + docType)); |
1778 | + } |
1779 | + |
1780 | + /** |
1781 | + * The method setUserFields() allows to set any AdditionalField to a user |
1782 | + * specific value. |
1783 | + * |
1784 | + * @param form |
1785 | + * a ProzesskopieForm object whose AdditionalField objects are |
1786 | + * subject to the change |
1787 | + * @param userFields |
1788 | + * the data to pass to the form |
1789 | + * @throws RuntimeException |
1790 | + * in case that no field with a matching title was found in the |
1791 | + * ProzesskopieForm object |
1792 | + */ |
1793 | + protected void setUserFields(ProzesskopieForm form, Map<String, String> userFields) throws RuntimeException { |
1794 | + |
1795 | + for (String key : userFields.keySet()) { |
1796 | + setAdditionalField(form, key, userFields.get(key)); |
1797 | + } |
1798 | + |
1799 | + } |
1800 | + |
1801 | + /** |
1802 | + * Sets the bibliographic data for a new process from a library catalogue. |
1803 | + * This is equal to manually choosing a catalogue and a search field, |
1804 | + * entering the search string and clicking “Apply”. |
1805 | + * |
1806 | + * Since the underlying OpacAuswerten() method doesn’t raise exceptions, we |
1807 | + * count the populated “additional details” fields before and after running |
1808 | + * the request and assume the method to have failed if not even one more |
1809 | + * field was populated by the method call. |
1810 | + * |
1811 | + * @param inputForm |
1812 | + * the ProzesskopieForm to be set |
1813 | + * @param id |
1814 | + * the ticket’s id |
1815 | + * @param opac |
1816 | + * the value for “Search in Opac” |
1817 | + * @param field |
1818 | + * the number of the search field, e.g. “12” for PPN. |
1819 | + * @param value |
1820 | + * the search string |
1821 | + * @throws RuntimeException |
1822 | + * is thrown if the search didn’t bring any results |
1823 | + */ |
1824 | + protected void getBibliorgaphicData(ProzesskopieForm inputForm, String opac, String field, String value) throws RuntimeException { |
1825 | + |
1826 | + inputForm.setOpacKatalog(opac); |
1827 | + inputForm.setOpacSuchfeld(field); |
1828 | + inputForm.setOpacSuchbegriff(value); |
1829 | + |
1830 | + int before = countPopulatedAdditionalFields(inputForm); |
1831 | + inputForm.OpacAuswerten(); |
1832 | + int afterwards = countPopulatedAdditionalFields(inputForm); |
1833 | + |
1834 | + if (!(afterwards > before)) |
1835 | + throw new RuntimeException("Searching the OPAC didn’t yield any results."); |
1836 | + } |
1837 | + |
1838 | + /** |
1839 | + * The function countPopulatedAdditionalFields() returns the number of |
1840 | + * AdditionalFields in the given ProzesskopieForm that have meaningful |
1841 | + * content. |
1842 | + * |
1843 | + * @param form |
1844 | + * a ProzesskopieForm object to examine |
1845 | + * @return the number of AdditionalFields populated |
1846 | + */ |
1847 | + protected int countPopulatedAdditionalFields(ProzesskopieForm form) { |
1848 | + int result = 0; |
1849 | + |
1850 | + for (AdditionalField field : form.getAdditionalFields()) { |
1851 | + String value = field.getWert(); |
1852 | + if (value != null && value.length() > 0) |
1853 | + result++; |
1854 | + } |
1855 | + |
1856 | + return result; |
1857 | + } |
1858 | + |
1859 | + /** |
1860 | + * The method setAdditionalField() sets the value of an AdditionalField held |
1861 | + * by a ProzesskopieForm object. |
1862 | + * |
1863 | + * @param inputForm |
1864 | + * a ProzesskopieForm object |
1865 | + * @param key |
1866 | + * the title of the AdditionalField whose value shall be modified |
1867 | + * @param value |
1868 | + * the new value for the AdditionalField |
1869 | + * @throws RuntimeException |
1870 | + * in case that no field with a matching title was found in the |
1871 | + * ProzesskopieForm object |
1872 | + */ |
1873 | + protected void setAdditionalField(ProzesskopieForm inputForm, String key, String value) throws RuntimeException { |
1874 | + |
1875 | + for (AdditionalField field : inputForm.getAdditionalFields()) { |
1876 | + if (key.equals(field.getTitel())) { |
1877 | + field.setWert(value); |
1878 | + return; |
1879 | + } |
1880 | + } |
1881 | + |
1882 | + throw new RuntimeException("Couldn’t set “" + key + "” to “" + value + "”: No such field in record."); |
1883 | + } |
1884 | + |
1885 | +} |
1886 | |
1887 | === added file 'src/org/goobi/mq/processors/FinaliseStepProcessor.java' |
1888 | --- src/org/goobi/mq/processors/FinaliseStepProcessor.java 1970-01-01 00:00:00 +0000 |
1889 | +++ src/org/goobi/mq/processors/FinaliseStepProcessor.java 2012-07-27 10:58:19 +0000 |
1890 | @@ -0,0 +1,97 @@ |
1891 | +/* |
1892 | + * This file is part of the Goobi Application - a Workflow tool for the support of |
1893 | + * mass digitization. |
1894 | + * |
1895 | + * Visit the websites for more information. |
1896 | + * - http://gdz.sub.uni-goettingen.de |
1897 | + * - http://www.goobi.org |
1898 | + * - http://launchpad.net/goobi-production |
1899 | + * |
1900 | + * This program is free software; you can redistribute it and/or modify it under |
1901 | + * the terms of the GNU General Public License as published by the Free Software |
1902 | + * Foundation; either version 2 of the License, or (at your option) any later |
1903 | + * version. |
1904 | + * |
1905 | + * This program is distributed in the hope that it will be useful, but WITHOUT ANY |
1906 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
1907 | + * PARTICULAR PURPOSE. See the GNU General Public License for more details. You |
1908 | + * should have received a copy of the GNU General Public License along with this |
1909 | + * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, |
1910 | + * Suite 330, Boston, MA 02111-1307 USA |
1911 | + */ |
1912 | + |
1913 | +package org.goobi.mq.processors; |
1914 | + |
1915 | +import org.apache.commons.lang.StringEscapeUtils; |
1916 | +import org.goobi.mq.ActiveMQProcessor; |
1917 | +import org.goobi.mq.MapMessageObjectReader; |
1918 | + |
1919 | +import de.sub.goobi.config.ConfigMain; |
1920 | +import de.sub.goobi.forms.AktuelleSchritteForm; |
1921 | +import de.sub.goobi.persistence.SchrittDAO; |
1922 | + |
1923 | +/** |
1924 | + * This is a web service interface to close steps. You have to provide the step |
1925 | + * id as “id”; you can add a field “message” which will be added to the wiki |
1926 | + * field. |
1927 | + * |
1928 | + * @author Matthias Ronge <matthias.ronge@zeutschel.de> |
1929 | + */ |
1930 | +public class FinaliseStepProcessor extends ActiveMQProcessor { |
1931 | + |
1932 | + /** |
1933 | + * The default constructor looks up the queue name to use in |
1934 | + * GoobiConfig.properties. If that is not configured and “null” is passed to |
1935 | + * the super constructor, this will prevent |
1936 | + * ActiveMQDirector.registerListeners() from starting this service. |
1937 | + */ |
1938 | + public FinaliseStepProcessor() { |
1939 | + super(ConfigMain.getParameter("activeMQ.finaliseStep.queue", null)); |
1940 | + } |
1941 | + |
1942 | + /** |
1943 | + * This is the main routine processing incoming tickets. It gets an |
1944 | + * AktuelleSchritteForm object, sets it to the appropriate step which is |
1945 | + * retrieved from the database, appends the message − if any − to the wiki |
1946 | + * field, and executes the form’s the step close function. |
1947 | + * |
1948 | + * @param ticket |
1949 | + * the incoming message |
1950 | + * |
1951 | + * @see org.goobi.mq.ActiveMQProcessor#process(org.goobi.mq.MapMessageObjectReader) |
1952 | + */ |
1953 | + protected void process(MapMessageObjectReader ticket) throws Exception { |
1954 | + AktuelleSchritteForm dialog = new AktuelleSchritteForm(); |
1955 | + Integer stepID = ticket.getMandatoryInteger("id"); |
1956 | + dialog.setMySchritt(new SchrittDAO().get(stepID)); |
1957 | + if (ticket.hasField("message")) |
1958 | + addMessageToWikiField(dialog, ticket.getString("message")); |
1959 | + dialog.SchrittDurchBenutzerAbschliessen(); |
1960 | + } |
1961 | + |
1962 | + /** |
1963 | + * The addMessageToWikiField() method is a helper method which composes the |
1964 | + * new wiki field using a StringBuilder. The message is encoded using HTML |
1965 | + * entities to prevent certain characters from playing merry havoc when the |
1966 | + * message box shall be rendered in a browser later. |
1967 | + * |
1968 | + * @param form |
1969 | + * the AktuelleSchritteForm which is the owner of the wiki field |
1970 | + * @param message |
1971 | + * the message to append |
1972 | + */ |
1973 | + protected void addMessageToWikiField(AktuelleSchritteForm form, String message) { |
1974 | + StringBuilder composer = new StringBuilder(); |
1975 | + String wikiField = form.getWikiField(); |
1976 | + if (wikiField != null && wikiField.length() > 0) { |
1977 | + composer.append(wikiField); |
1978 | + composer.append("\r\n"); |
1979 | + } |
1980 | + composer.append("<p>"); |
1981 | + composer.append(StringEscapeUtils.escapeHtml(message)); |
1982 | + composer.append("</p>"); |
1983 | + form.setWikiField(composer.toString()); |
1984 | + return; |
1985 | + } |
1986 | + |
1987 | +} |
1988 | |
1989 | === modified file 'src/org/goobi/production/flow/statistics/hibernate/FilterHelper.java' |
1990 | --- src/org/goobi/production/flow/statistics/hibernate/FilterHelper.java 2012-02-22 07:43:02 +0000 |
1991 | +++ src/org/goobi/production/flow/statistics/hibernate/FilterHelper.java 2012-07-27 10:58:19 +0000 |
1992 | @@ -94,7 +94,7 @@ |
1993 | /* identify current user */ |
1994 | LoginForm login = (LoginForm) Helper |
1995 | .getManagedBeanValue("#{LoginForm}"); |
1996 | - if (login.getMyBenutzer() == null) |
1997 | + if (login == null || login.getMyBenutzer() == null) |
1998 | return; |
1999 | /* init id-list, preset with item 0 */ |
2000 | List<Integer> idList = new ArrayList<Integer>(); |