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    }