001 /*
002 *
003 * $Revision: 15604 $ $Date: 2009-07-24 09:48:26 +0200 (Fri, 24 Jul 2009) $
004 *
005 * This file is part of *** M y C o R e ***
006 * See http://www.mycore.de/ for details.
007 *
008 * This program is free software; you can use it, redistribute it
009 * and / or modify it under the terms of the GNU General Public License
010 * (GPL) as published by the Free Software Foundation; either version 2
011 * of the License or (at your option) any later version.
012 *
013 * This program is distributed in the hope that it will be useful, but
014 * WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program, in a file called gpl.txt or license.txt.
020 * If not, write to the Free Software Foundation Inc.,
021 * 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA
022 */
023
024 // package
025 package org.mycore.frontend.workflow;
026
027 import static org.mycore.common.MCRConstants.XLINK_NAMESPACE;
028
029 import java.io.File;
030 import java.io.FilenameFilter;
031 import java.io.IOException;
032 import java.net.HttpURLConnection;
033 import java.net.MalformedURLException;
034 import java.net.URL;
035 import java.util.ArrayList;
036 import java.util.Hashtable;
037 import java.util.List;
038 import java.util.StringTokenizer;
039
040 import org.apache.log4j.Logger;
041 import org.jdom.Document;
042 import org.jdom.Element;
043 import org.jdom.xpath.XPath;
044 import org.mycore.common.MCRConfiguration;
045 import org.mycore.common.MCRException;
046 import org.mycore.common.MCRUtils;
047 import org.mycore.common.xml.MCRXMLHelper;
048 import org.mycore.datamodel.common.MCRActiveLinkException;
049 import org.mycore.datamodel.metadata.MCRDerivate;
050 import org.mycore.datamodel.metadata.MCRMetaIFS;
051 import org.mycore.datamodel.metadata.MCRMetaLinkID;
052 import org.mycore.datamodel.metadata.MCRObject;
053 import org.mycore.datamodel.metadata.MCRObjectID;
054 import org.mycore.datamodel.metadata.MCRObjectService;
055 import org.mycore.datamodel.metadata.validator.MCREditorOutValidator;
056 import org.mycore.frontend.cli.MCRDerivateCommands;
057 import org.mycore.frontend.cli.MCRObjectCommands;
058 import org.mycore.frontend.servlets.MCRServlet;
059
060 /**
061 * This class holds methods to manage the workflow file system of MyCoRe.
062 *
063 * @author Jens Kupferschmidt
064 * @version $Revision: 15604 $ $Date: 2009-07-24 09:48:26 +0200 (Fri, 24 Jul 2009) $
065 */
066
067 public class MCRSimpleWorkflowManager {
068
069 /** The link table manager singleton */
070 protected static MCRSimpleWorkflowManager singleton;
071
072 // Configuration
073 private static MCRConfiguration config = null;
074
075 // logger
076 static Logger logger = Logger.getLogger(MCRSimpleWorkflowManager.class.getName());
077
078 // The mail sender address
079 static String sender = "";
080
081 // table of workflow directories mail addresses
082 private Hashtable<String, File> ht = null;
083
084 private Hashtable<String, ArrayList<String>> mt = null;
085
086 /**
087 * Returns the workflow manager singleton.
088 */
089 public static synchronized MCRSimpleWorkflowManager instance() {
090 if (singleton == null) {
091 singleton = new MCRSimpleWorkflowManager();
092 }
093
094 return singleton;
095 }
096
097 /**
098 * The constructor of this class.
099 */
100 protected MCRSimpleWorkflowManager() {
101 config = MCRConfiguration.instance();
102
103 // read mail sender address
104 sender = config.getString("MCR.Mail.Address", "mcradmin@localhost");
105
106 // int tables
107 ht = new Hashtable<String, File>();
108 mt = new Hashtable<String, ArrayList<String>>();
109 }
110
111 /**
112 * The method return the workflow directory path for a given MCRObjectID
113 * type.
114 *
115 * @param type
116 * the MCRObjectID type
117 * @return the string of the workflow directory path
118 */
119 public final File getDirectoryPath(String base) {
120 if (ht.containsKey(base)) {
121 return ht.get(base);
122 }
123 String dirname = config.getString("MCR.SWF.Directory." + base, null);
124 if (dirname == null) {
125 int ibase = base.indexOf('_');
126 if (ibase != -1) {
127 String type = base.substring(ibase + 1);
128 dirname = config.getString("MCR.SWF.Directory." + type, null);
129 }
130 if (dirname == null) {
131 final File currentDir = new File(".");
132 ht.put(base, currentDir);
133 logger.warn("No workflow directory path of " + base + " is in the configuration.");
134 return currentDir;
135 }
136 }
137 File dir = new File(dirname);
138 if (!dir.exists()) {
139 dir.mkdirs();
140 }
141 ht.put(base, dir);
142 return dir;
143 }
144
145 /**
146 * The method return the information mail address for a given MCRObjectID
147 * type.
148 *
149 * @param base
150 * the MCRObjectID base or MCRObjectID type
151 * @param todo
152 * the todo action String from the workflow.
153 * @return the List of the information mail addresses
154 */
155 public final List<String> getMailAddress(String base, String todo) {
156 if ((base == null) || ((base = base.trim()).length() == 0)) {
157 return new ArrayList<String>();
158 }
159
160 if ((todo == null) || ((todo = todo.trim()).length() == 0)) {
161 return new ArrayList<String>();
162 }
163
164 if (mt.containsKey(base + "_" + todo)) {
165 return mt.get(base + "_" + todo);
166 }
167
168 String mailaddr = config.getString("MCR.SWF.Mail." + base + "." + todo, "");
169 ArrayList<String> li = new ArrayList<String>();
170
171 if ((mailaddr == null) || ((mailaddr = mailaddr.trim()).length() == 0)) {
172 int i = base.indexOf('_');
173 if (i != -1) {
174 String type = base.substring(i + 1);
175 mailaddr = config.getString("MCR.SWF.Mail." + type + "." + todo, "");
176 if ((mailaddr == null) || ((mailaddr = mailaddr.trim()).length() == 0)) {
177 mt.put(base, li);
178 logger.warn("No mail address for MCR.SWF.Mail." + base + "." + todo + " is in the configuration.");
179 return li;
180 }
181 } else {
182 mt.put(base, li);
183 logger.warn("No mail address for MCR.SWF.Mail." + base + "." + todo + " is in the configuration.");
184 return li;
185 }
186 }
187
188 StringTokenizer st = new StringTokenizer(mailaddr, ",");
189 while (st.hasMoreTokens()) {
190 li.add(st.nextToken());
191 }
192 mt.put(base, li);
193
194 return li;
195 }
196
197 /**
198 * The method return the mail sender adress form the configuration.
199 *
200 * @return the mail sender adress
201 */
202 public final String getMailSender() {
203 return sender;
204 }
205
206 /**
207 * The method return a ArrayList of file names from objects they are under
208 * .../workflow/ <em>type/...type...</em>.
209 *
210 * @param base
211 * the MCRObjectID base attribute
212 * @return an ArrayList of file names
213 */
214 public final ArrayList<String> getAllObjectFileNames(String base) {
215 File dir = getDirectoryPath(base);
216 ArrayList<String> workfiles = new ArrayList<String>();
217
218 String[] dirl = null;
219
220 if (dir.isDirectory()) {
221 dirl = dir.list();
222 }
223
224 if (dirl != null) {
225 for (int i = 0; i < dirl.length; i++) {
226 if ((dirl[i].indexOf(base) != -1) && (dirl[i].endsWith(".xml"))) {
227 workfiles.add(dirl[i]);
228 }
229 }
230 }
231
232 java.util.Collections.sort(workfiles);
233
234 return workfiles;
235 }
236
237 /**
238 * The method return a ArrayList of file names form derivates they are under
239 * .../workflow/ <em>type/...derivate...</em>.
240 *
241 * @param type
242 * the MCRObjectID type attribute
243 * @return an ArrayList of file names
244 */
245 public final ArrayList<String> getAllDerivateFileNames(String base) {
246 File dir = getDirectoryPath(base);
247 ArrayList<String> workfiles = new ArrayList<String>();
248 String[] dirl = null;
249
250 if (dir.isDirectory()) {
251 dirl = dir.list();
252 }
253
254 if (dirl != null) {
255 for (int i = 0; i < dirl.length; i++) {
256 if ((dirl[i].indexOf("_derivate_") != -1) && (dirl[i].endsWith(".xml"))) {
257 workfiles.add(dirl[i]);
258 }
259 }
260 }
261 java.util.Collections.sort(workfiles);
262 return workfiles;
263 }
264
265 /**
266 * The method read a derivate file with name <em>filename</em> in the
267 * workflow directory of <em>type</em> and check that this derivate
268 * reference the given <em>ID</em>.
269 *
270 * @param filename
271 * the file name of the derivate
272 * @param ID
273 * the MCRObjectID of the metadata object
274 * @return true if the derivate refernce the metadata object, else return
275 * false
276 */
277 public final boolean isDerivateOfObject(String filename, MCRObjectID ID) {
278 File dir = getDirectoryPath(ID.getBase());
279 File fname = new File(dir, filename);
280 org.jdom.Document workflow_in = null;
281
282 try {
283 workflow_in = MCRXMLHelper.parseURI(fname.toURI());
284 logger.debug("Readed from workflow " + fname);
285 } catch (Exception ex) {
286 logger.error("Error while reading XML workflow file " + filename);
287 logger.error(ex.getMessage());
288
289 return false;
290 }
291
292 org.jdom.Element root = workflow_in.getRootElement();
293 org.jdom.Element derivate = root.getChild("derivate");
294
295 if (derivate == null) {
296 return false;
297 }
298
299 org.jdom.Element linkmetas = derivate.getChild("linkmetas");
300
301 if (linkmetas == null) {
302 return false;
303 }
304
305 org.jdom.Element linkmeta = linkmetas.getChild("linkmeta");
306
307 if (linkmeta == null) {
308 return false;
309 }
310
311 String DID = linkmeta.getAttributeValue("href", XLINK_NAMESPACE);
312 logger.debug("The linked object ID of derivate is " + DID);
313
314 if (!ID.getId().equals(DID)) {
315 return false;
316 }
317 return true;
318 }
319
320 /**
321 * The method removes a metadata object with all referenced derivate objects
322 * from the workflow.
323 *
324 * @param ID
325 * the MCRObjectID of the metadata object
326 */
327 public final void deleteMetadataObject(MCRObjectID ID) {
328 // remove metadate
329 String fn = getDirectoryPath(ID.getBase()) + File.separator + ID + ".xml";
330
331 try {
332 File fi = new File(fn);
333
334 if (fi.isFile() && fi.canWrite()) {
335 fi.delete();
336 logger.debug("File " + fn + " removed.");
337 } else {
338 logger.error("Can't remove file " + fn);
339 }
340 } catch (Exception ex) {
341 logger.error("Can't remove file " + fn);
342 }
343
344 // remove derivate
345 ArrayList<String> derifiles = getAllDerivateFileNames(ID.getBase());
346
347 for (int i = 0; i < derifiles.size(); i++) {
348 String dername = derifiles.get(i);
349 logger.debug("Check the derivate file " + dername);
350
351 if (isDerivateOfObject(dername, ID)) {
352 try {
353 MCRObjectID DID = new MCRObjectID(dername.substring(0, dername.length() - 4));
354
355 deleteDerivateObject(ID, DID);
356 } catch (MCRException ex) {
357 }
358 }
359 }
360 }
361
362 /**
363 * The method removes a derivate object from the workflow.
364 *
365 * @param ID
366 * the MCRObjectID type of the metadata object
367 * @param DID
368 * the MCRObjectID of the derivate object as String
369 */
370 public final void deleteDerivateObject(MCRObjectID ID, MCRObjectID DID) {
371 logger.debug("Delete the derivate " + DID.getId());
372 // remove the XML file
373 String fn = getDirectoryPath(ID.getBase()) + File.separator + DID.getId();
374 try {
375 File fi = new File(fn + ".xml");
376
377 if (fi.isFile() && fi.canWrite()) {
378 fi.delete();
379 logger.debug("File " + fn + ".xml removed.");
380 } else {
381 logger.error("Can't remove file " + fn + ".xml");
382 }
383 } catch (Exception ex) {
384 logger.error("Can't remove file " + fn + ".xml");
385 }
386 // remove all derivate objects
387 try {
388 File fi = new File(fn);
389 if (fi.isDirectory() && fi.canWrite()) {
390 // delete files
391 ArrayList<String> dellist = MCRUtils.getAllFileNames(fi);
392
393 for (int j = 0; j < dellist.size(); j++) {
394 String na = (String) dellist.get(j);
395 File fl = new File(fn + File.separator + na);
396
397 if (fl.delete()) {
398 logger.debug("File " + na + " removed.");
399 } else {
400 logger.error("Can't remove file " + na);
401 }
402 }
403 // delete subirectories
404 dellist = MCRUtils.getAllDirectoryNames(fi);
405
406 for (int j = dellist.size() - 1; j > -1; j--) {
407 String na = (String) dellist.get(j);
408 File fl = new File(fn + File.separator + na);
409
410 if (fl.delete()) {
411 logger.debug("Directory " + na + " removed.");
412 } else {
413 logger.error("Can't remove directory " + na);
414 }
415 }
416 if (fi.delete()) {
417 logger.debug("Directory " + fn + " removed.");
418 } else {
419 logger.error("Can't remove directory " + fn);
420 }
421 } else {
422 logger.error("Can't remove directory " + fn);
423 }
424 } catch (Exception ex) {
425 logger.error("Can't remove directory " + fn.substring(0, fn.length() - 4));
426 }
427 }
428
429 /**
430 * The method commit a metadata object with all referenced derivate objects
431 * from the workflow to the data store.
432 *
433 * @param ID
434 * the ID of the metadata object
435 * @throws MCRActiveLinkException
436 * if links to the object exist prior loading
437 */
438 public final boolean commitMetadataObject(MCRObjectID ID) throws MCRActiveLinkException {
439 // commit metadata
440 String fn = getDirectoryPath(ID.getBase()) + File.separator + ID + ".xml";
441
442 if (MCRObject.existInDatastore(ID)) {
443 MCRObjectCommands.updateFromFile(fn, false);
444 } else {
445 MCRObjectCommands.loadFromFile(fn, false);
446 }
447
448 logger.info("The metadata objekt was " + fn + " loaded.");
449 // commit derivates
450 if (!MCRObject.existInDatastore(ID)) {
451 return false;
452 }
453
454 ArrayList<String> derifiles = getAllDerivateFileNames(ID.getBase());
455
456 for (int i = 0; i < derifiles.size(); i++) {
457 String dername = derifiles.get(i);
458 logger.debug("Check the derivate file " + dername);
459
460 if (isDerivateOfObject(dername, ID)) {
461 fn = getDirectoryPath(ID.getBase()) + File.separator + dername;
462
463 if (!loadDerivate(ID.getId(), fn)) {
464 return false;
465 }
466 }
467 }
468
469 return true;
470 }
471
472 /**
473 * The method commit a derivate object with update method from the workflow
474 * to the data store.
475 *
476 * @param ID
477 * the MCRObjectID as String of the derivate object
478 */
479 public final boolean commitDerivateObject(MCRObjectID ID) {
480 String fn = getDirectoryPath(ID.getBase()) + File.separator + ID.getId() + ".xml";
481
482 return loadDerivate(ID.getId(), fn);
483 }
484
485 private boolean loadDerivate(String ID, String filename) {
486 if (MCRDerivate.existInDatastore(ID)) {
487 MCRDerivateCommands.updateFromFile(filename, false);
488 } else {
489 MCRDerivateCommands.loadFromFile(filename, false);
490 }
491
492 if (!MCRDerivate.existInDatastore(ID)) {
493 return false;
494 }
495
496 logger.debug("Commit the derivate " + filename);
497
498 return true;
499 }
500
501 /**
502 * The method return the next free derivate ID. It looks in the current
503 * workflow directory and in the server.
504 */
505 public synchronized final MCRObjectID getNextDrivateID(MCRObjectID ID) {
506 final String myproject = ID.getProjectId() + "_derivate";
507
508 MCRObjectID dmcridnext = new MCRObjectID();
509 dmcridnext.setNextFreeId(myproject);
510
511 File workdir = getDirectoryPath(ID.getBase());
512 String max = myproject + "_0.xml";
513 for (String file : workdir.list(new FilenameFilter() {
514 public boolean accept(File dir, String name) {
515 return name.startsWith(myproject) && name.endsWith(".xml");
516 }
517 })) {
518 if (file.compareTo(max) > 0)
519 max = file;
520 }
521 int maxIDinWorkflow = Integer.parseInt( max.substring( max.lastIndexOf( "_" ) + 1, max.length() - 4 ) );
522
523 MCRObjectID mcridnext = new MCRObjectID();
524 mcridnext.setNextFreeId(myproject, maxIDinWorkflow );
525 return mcridnext;
526 }
527
528 /**
529 * The method create a new MCRDerivate and store them to the directory of
530 * the workflow that correspons with the type of the given object
531 * MCRObjectID with the name of itseslf. Also ti create a ne directory with
532 * the same new name. This new derivate ID was returned.
533 *
534 * @param ID
535 * the MCRObjectID of the related object
536 * @param DD
537 * the MCRObjectID of the related derivate
538 * @return the MCRObjectID of the derivate
539 */
540 public final MCRDerivate createDerivate(MCRObjectID ID, MCRObjectID DD) {
541 // build the derivate XML file
542 MCRDerivate der = new MCRDerivate();
543 der.setId(DD);
544 der.setLabel("Dataobject from " + ID.getId());
545 der.setSchema("datamodel-derivate.xsd");
546
547 MCRMetaLinkID link = new MCRMetaLinkID("linkmetas", "linkmeta", "de", 0);
548 link.setReference(ID.getId(), "", "");
549 der.getDerivate().setLinkMeta(link);
550
551 MCRMetaIFS internal = new MCRMetaIFS("internals", "internal", "de", DD.getId());
552 internal.setMainDoc("");
553 der.getDerivate().setInternals(internal);
554
555 MCRObjectService service = new MCRObjectService();
556 org.jdom.Element elm = service.createXML();
557 MCREditorOutValidator.setDefaultDerivateACLs(elm);
558 service.setFromDOM(elm);
559 der.setService(service);
560
561 return der;
562 }
563
564 /**
565 * The method return the conditione XML tree from a XML file in the workflow
566 * for a given permission.
567 *
568 * @param id
569 * the MCRObjectID
570 * @param permission
571 * the permission for the ACL system
572 * @return the XML tree of the condition or null if the permission is not defined
573 */
574 public final org.jdom.Element getRuleFromFile(MCRObjectID mcrid, String permission) {
575 // read data
576 String fn = getDirectoryPath(mcrid.getBase()) + File.separator + mcrid.getId() + ".xml";
577 try {
578 File fi = new File(fn);
579 if (fi.isFile() && fi.canRead()) {
580 Document wfDoc = MCRXMLHelper.parseURI(fi.toURI(), false);
581 XPath path = XPath.newInstance("/*/service/servacls/servacl[@permission='" + permission + "']/condition");
582 @SuppressWarnings("unchecked")
583 List<Element> results = path.selectNodes(wfDoc);
584 if (results.size() > 0) {
585 return (Element) ((Element) results.get(0)).detach();
586 }
587 } else {
588 logger.error("Can't read file " + fn);
589 }
590 } catch (Exception ex) {
591 logger.error("Can't read file " + fn);
592 }
593 return null;
594 }
595
596 /**
597 * The method return page name of the next URL of the workflow.
598 * @param pagedir the base directory of the WEB application
599 * @param base the MCRObjectID base ID
600 * @return the workflow URL
601 */
602 public final String getWorkflowFile(String pagedir, String base) {
603 StringBuffer sb = new StringBuffer();
604 sb.append(pagedir).append("editor_").append(base).append("_editor.xml");
605 try {
606 URL url = new URL(MCRServlet.getBaseURL() + sb.toString());
607 HttpURLConnection http = (HttpURLConnection) url.openConnection();
608 if (http.getResponseCode() != 200) {
609 int i = base.indexOf('_');
610 sb = new StringBuffer();
611 sb.append(pagedir).append("editor_").append(base.substring(i + 1)).append("_editor.xml");
612 url = new URL(MCRServlet.getBaseURL() + sb.toString());
613 http = (HttpURLConnection) url.openConnection();
614 if (http.getResponseCode() != 200) {
615 sb = new StringBuffer("");
616 }
617 }
618 } catch (MalformedURLException e) {
619 sb = new StringBuffer("");
620 } catch (IOException e) {
621 sb = new StringBuffer("");
622 }
623 return sb.toString();
624 }
625
626 }