001    package org.mycore.frontend.redundancy.cli;
002    
003    import java.io.File;
004    import java.io.FileOutputStream;
005    import java.util.ArrayList;
006    import java.util.Collection;
007    import java.util.Iterator;
008    import java.util.List;
009    
010    import org.apache.log4j.Logger;
011    import org.jdom.Document;
012    import org.jdom.Element;
013    import org.jdom.Namespace;
014    import org.jdom.filter.ElementFilter;
015    import org.jdom.filter.Filter;
016    import org.jdom.input.SAXBuilder;
017    import org.jdom.output.Format;
018    import org.jdom.output.XMLOutputter;
019    import org.mycore.access.MCRAccessManager;
020    import org.mycore.datamodel.common.MCRLinkTableManager;
021    import org.mycore.datamodel.metadata.MCRObject;
022    import org.mycore.frontend.redundancy.MCRRedundancyUtil;
023    
024    /**
025     * Processes a redundancy xml-file to relink and delete duplicate mcr objects in the database.<br>
026     * Commandprocess:<br>
027     * (cleanUp [(process redundancy object[(replace links)* -> (delete mcrobject) -> (delete redundancy object xml entry)])*] -> [update xml document]) 
028     * @author Matthias Eichner
029     */
030    public class MCRRedundancyCleanUpCommand {
031    
032        private static final Logger LOGGER = Logger.getLogger(MCRRedundancyCleanUpCommand.class);
033    
034        /**
035         * The xml document is stored for the whole process.
036         */
037        private static Document document;
038    
039        /**
040         * The start method.
041         * @param type The type.
042         * @return A commandlist of redundancy objects which have to processed.
043         * @throws Exception
044         */
045        public static List<String> cleanUp(String type) throws Exception {
046            // create the commands list
047            List<String> commands = new ArrayList<String>();
048    
049            // try to get the right file
050            File file = new File(MCRRedundancyUtil.DIR + "redundancy-" + type + ".xml");
051            if (!file.exists()) {
052                LOGGER.error("Couldnt find the file of type: " + type);
053                LOGGER.error("A file like 'redundancy-person.xml' in 'build/webapps/doubletFinder' is necessary.");
054                return commands;
055            }
056            LOGGER.info("Redundancy file found: " + file);
057    
058            // get the xml document
059            SAXBuilder builder = new SAXBuilder();
060            document = builder.build(file);
061            Element rootElement = document.getRootElement();
062            Filter redunObjectFilter = new ElementFilter("redundancyObjects");
063    
064            LOGGER.info("Start to replaces links and delete duplicate objects.");
065    
066            // pass through the redundancyObjects elements
067            List list = rootElement.getContent(redunObjectFilter);
068            for (Object o : list) {
069                Element redunElement = (Element) o;
070                String status = redunElement.getAttributeValue("status");
071                if (status != null && status.equals("closed")) {
072                    // add the redundancy object to commands list
073                    commands.add("internal process redundancy object " + redunElement.getAttributeValue("id"));
074                }
075            }
076            if (commands.size() > 0)
077                commands.add("internal update xml document " + file.getAbsolutePath());
078            else if (list.size() == 0)
079                LOGGER.info("Redundancy xml file is empty.");
080            else
081                LOGGER.info("Redundancy Xml file has no 'closed' entries.");
082            return commands;
083        }
084    
085        /**
086         * Creates the commands of an redundancy Object.
087         * @param id The id of the redundancyObjects element.
088         * @return A list of commands.
089         * @throws Exception
090         */
091        public static List<String> processRedundancyObject(String id) throws Exception {
092            Element redunElement = getRedunElementOfId(id);
093            Element originalElement = null;
094            ArrayList<Element> duplicateElements = new ArrayList<Element>();
095    
096            Filter objectFilter = new ElementFilter("object");
097            for (Object o : redunElement.getContent(objectFilter)) {
098                Element objectElement = (Element) o;
099                String status = objectElement.getAttributeValue("status");
100                if (status == null)
101                    continue;
102                if (status.equals("nonDoublet")) {
103                    originalElement = objectElement;
104                } else if (status.equals("doublet")) {
105                    duplicateElements.add(objectElement);
106                }
107            }
108            List<String> commands = createLinkAndDeleteCommands(originalElement, duplicateElements);
109            commands.add("internal delete redundancy object xml entry " + id);
110            return commands;
111        }
112    
113        /**
114         * Deletes an processed redundancy element entry in the xml document.
115         * @param id The id of the element.
116         */
117        public static void deleteRedundancyElementEntry(String id) {
118            Element e = getRedunElementOfId(id);
119            if (e.getParent() != null)
120                e.getParent().removeContent(e);
121        }
122    
123        /**
124         * Creates the link- and delete commands for an redundancyElement
125         */
126        private static List<String> createLinkAndDeleteCommands(Element originalElement, ArrayList<Element> duplicateElements) throws Exception {
127            List<String> commands = new ArrayList<String>();
128            if (originalElement == null || duplicateElements.size() == 0)
129                return commands;
130    
131            String originalObjectId = originalElement.getAttributeValue("objId");
132            for (Element duplicateElement : duplicateElements) {
133                String dupObjectId = duplicateElement.getAttributeValue("objId");
134                Collection<String> list = MCRLinkTableManager.instance().getSourceOf(dupObjectId, "reference");
135                for (String source : list) {
136                    // add replace command
137                    commands.add("internal replace links " + source + " " + dupObjectId + " " + originalObjectId);
138                }
139                // add delete command
140                commands.add("delete object " + dupObjectId);
141            }
142            return commands;
143        }
144    
145        /**
146         * Returns an redundancy object by the specified id.
147         * @param id The id.
148         * @return The element or null. 
149         */
150        protected static Element getRedunElementOfId(String id) {
151            Filter filter = new ElementFilter("redundancyObjects");
152            List list = document.getRootElement().getContent(filter);
153            for (Object o : list) {
154                Element e = (Element) o;
155                if (e.getAttributeValue("id") != null && e.getAttributeValue("id").equals(id)) {
156                    return e;
157                }
158            }
159            return null;
160        }
161    
162        /**
163         * Replaces all links which are found in the source mcrobject xml-tree.
164         * @param source The source Id as String.
165         * @param oldLink The link which to replaced.
166         * @param newLink The new link.
167         */
168        public static void replaceLinks(String sourceId, String oldLink, String newLink) throws Exception {
169            if (!MCRAccessManager.checkPermission(sourceId, "writedb")) {
170                LOGGER.error("The current user has not the permission to modify " + sourceId);
171                return;
172            }
173    
174            MCRObject sourceMCRObject = new MCRObject();
175            sourceMCRObject.receiveFromDatastore(sourceId);
176    
177            // ArrayList for equal elements
178            ArrayList<Element> equalElements = new ArrayList<Element>();
179    
180            Namespace ns = Namespace.getNamespace("xlink", "http://www.w3.org/1999/xlink");
181            Filter oldLinkFilter = new AttributeValueFilter("href", ns, oldLink);
182            Document doc = sourceMCRObject.createXML();
183            Iterator i = doc.getDescendants(oldLinkFilter);
184            while (i.hasNext()) {
185                Element e = (Element) i.next();
186                e.setAttribute("href", newLink, ns);
187                /*  It is possible, that an updated element is equal with an existing element.
188                    In that case it is necessary to delete the new element. */
189                if (isElementAlreadyExists(e)) {
190                    equalElements.add(e);
191                }
192            }
193            // delete equal elements
194            for (Element e : equalElements) {
195                Element parent = e.getParentElement();
196                parent.removeContent(e);
197            }
198            sourceMCRObject.setFromJDOM(doc);
199            sourceMCRObject.updateInDatastore();
200            LOGGER.info("Links replaced of source " + sourceId + ": " + oldLink + " -> " + newLink);
201        }
202    
203        /**
204         * Checks if the element is equal to an element from the same parent.
205         * @param element The element to check.
206         * @return If the element in the parent already exists.
207         */
208        protected static boolean isElementAlreadyExists(Element element) {
209            Element parent = element.getParentElement();
210            Filter filter = new ElementFilter(element.getName());
211            for (Object o : parent.getContent(filter)) {
212                Element child = (Element) o;
213                // only different instances
214                if (element == child)
215                    continue;
216    
217                // bad compare, but jdom doesnt support a better solution
218                if (element.getName().equals(child.getName()) && element.getAttributes().toString().equals(child.getAttributes().toString())) {
219                    return true;
220                }
221            }
222            return false;
223        }
224    
225        /**
226         * Updates the specified file with the static document xml-structure.
227         * @param fileName
228         * @throws Exception
229         */
230        public static void updateXMLDocument(String fileName) throws Exception {
231            // write the updated xml document to the file system
232            XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
233            FileOutputStream output = new FileOutputStream(fileName);
234            outputter.output(document, output);
235        }
236    
237        /**
238         * A jdom-filter which compares attribute values.
239         */
240        protected static class AttributeValueFilter implements Filter {
241            protected String attrKey;
242    
243            protected String attrValue;
244    
245            protected Namespace ns;
246    
247            public AttributeValueFilter(String attrKey, Namespace ns, String attrValue) {
248                this.attrKey = attrKey;
249                this.attrValue = attrValue;
250                this.ns = ns;
251    
252            }
253    
254            public boolean matches(Object arg0) {
255                if (!(arg0 instanceof Element))
256                    return false;
257                Element e = (Element) arg0;
258                String value = e.getAttributeValue(attrKey, ns);
259                if (value != null && value.equals(attrValue))
260                    return true;
261                return false;
262            }
263        }
264    }