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 }