View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.frontend.cli;
20  
21  import java.io.IOException;
22  import java.text.MessageFormat;
23  import java.util.Collection;
24  import java.util.List;
25  import java.util.Locale;
26  
27  import org.jaxen.JaxenException;
28  import org.jdom2.Document;
29  import org.jdom2.Element;
30  import org.jdom2.Namespace;
31  import org.jdom2.filter.Filter;
32  import org.jdom2.filter.Filters;
33  import org.jdom2.xpath.XPathExpression;
34  import org.jdom2.xpath.XPathFactory;
35  import org.mycore.access.MCRAccessException;
36  import org.mycore.common.MCRConstants;
37  import org.mycore.common.MCRPersistenceException;
38  import org.mycore.common.config.MCRConfiguration2;
39  import org.mycore.common.content.MCRJDOMContent;
40  import org.mycore.common.xml.MCRNodeBuilder;
41  import org.mycore.datamodel.metadata.MCRMetadataManager;
42  import org.mycore.datamodel.metadata.MCRObject;
43  import org.mycore.datamodel.metadata.MCRObjectID;
44  import org.mycore.frontend.cli.annotation.MCRCommand;
45  import org.mycore.frontend.cli.annotation.MCRCommandGroup;
46  
47  /**
48   * Commands to batch add/remove/replace values
49   * like identifiers, categories, tags, flags etc. within XML.
50   * Supported fields are completely configurable using XPath expressions.
51   *
52   * @author Frank L\u00FCtzenkirchen
53   */
54  @MCRCommandGroup(name = "Batch Editor")
55  public class MCRBatchEditorCommands extends MCRAbstractCommands {
56  
57      private static final Collection<Namespace> NS = MCRConstants.getStandardNamespaces();
58  
59      private static final Filter<Element> FE = Filters.element();
60  
61      private static final String CFG_PREFIX = "MCR.BatchEditor.";
62  
63      private static final String CFG_PREFIX_BASE = CFG_PREFIX + "BaseLevel.";
64  
65      private static final String CFG_SUFFIX_REMOVE = ".Path2Remove";
66  
67      private static final String CFG_SUFFIX_ADD = ".Path2Add";
68  
69      private enum Action {
70          ADD, ADD_IF, REMOVE, REMOVE_IF, REPLACE
71      }
72  
73      @MCRCommand(syntax = "edit {0} at {1} add {2} {3}",
74          help = "Edit XML elements in object {0} at level {1} in object {1}, add field {2} with value {3}",
75          order = 2)
76      public static void batchAdd(String oid, String level, String field, String value)
77          throws JaxenException, MCRPersistenceException, MCRAccessException, IOException {
78          edit(oid, level, Action.ADD, field, value, null, null);
79      }
80  
81      @MCRCommand(syntax = "edit {0} at {1} if {2} {3} add {4} {5}",
82          help = "Edit XML elements in object {0} at level {1}, if there is a field {2} with value {3}, "
83              + "add field {4} with value {5}",
84          order = 1)
85      public static void batchAddIf(String oid, String level, String fieldIf, String valueIf, String field2Add,
86          String value2Add)
87          throws JaxenException, MCRPersistenceException, MCRAccessException, IOException {
88          edit(oid, level, Action.ADD_IF, fieldIf, valueIf, field2Add, value2Add);
89      }
90  
91      @MCRCommand(syntax = "edit {0} at {1} remove {2} {3}",
92          help = "Edit XML elements at in object {0} at level {1}, remove field {2} where value is {3}",
93          order = 2)
94      public static void batchRemove(String oid, String level, String field, String value)
95          throws MCRPersistenceException, MCRAccessException, JaxenException, IOException {
96          edit(oid, level, Action.REMOVE, field, value, null, null);
97      }
98  
99      @MCRCommand(syntax = "edit {0} at {1} if {2} {3} remove {4} {5}",
100         help = "Edit XML elements in object {0} at level {1}, if there is a field {2} with value {3}, "
101             + "remove field {4} where value is {5}",
102         order = 1)
103     public static void batchRemoveIf(String oid, String level, String fieldIf, String valueIf, String field2Rem,
104         String value2Rem)
105         throws MCRPersistenceException, MCRAccessException, JaxenException, IOException {
106         edit(oid, level, Action.REMOVE_IF, fieldIf, valueIf, field2Rem, value2Rem);
107     }
108 
109     @MCRCommand(syntax = "edit {0} at {1} replace {2} {3} with {4} {5}",
110         help = "Edit XML elements in object {0} at level {1}, replace field {2} value {3} by field {4} with value {5}",
111         order = 1)
112     public static void batchReplace(String oid, String level, String oldField, String oldValue, String newField,
113         String newValue)
114         throws JaxenException, MCRPersistenceException, MCRAccessException, IOException {
115         edit(oid, level, Action.REPLACE, oldField, oldValue, newField, newValue);
116     }
117 
118     public static void edit(String oid, String level, Action a, String field1, String value1, String field2,
119         String value2)
120         throws JaxenException, MCRPersistenceException, MCRAccessException, IOException {
121         Document xmlOld = getObjectXML(oid);
122         Document xmlNew = xmlOld.clone();
123 
124         for (Element base : getlevelElements(xmlNew, level)) {
125             if (a == Action.ADD) {
126                 add(base, field1, value1);
127             } else if (a == Action.REMOVE) {
128                 find(base, field1, value1).forEach(e -> e.detach());
129             } else if (a == Action.REPLACE) {
130                 List<Element> found = find(base, field1, value1);
131                 found.forEach(e -> e.detach());
132                 if (!found.isEmpty()) {
133                     add(base, field2, value2);
134                 }
135             } else if (a == Action.ADD_IF) {
136                 if (!find(base, field1, value1).isEmpty()) {
137                     add(base, field2, value2);
138                 }
139             } else if (a == Action.REMOVE_IF) {
140                 if (!find(base, field1, value1).isEmpty()) {
141                     find(base, field2, value2).forEach(e -> e.detach());
142                 }
143             }
144         }
145 
146         saveIfModified(xmlOld, xmlNew);
147     }
148 
149     private static Document getObjectXML(String objectID) {
150         MCRObjectID oid = MCRObjectID.getInstance(objectID);
151         MCRObject obj = MCRMetadataManager.retrieveMCRObject(oid);
152         return obj.createXML();
153     }
154 
155     private static void saveIfModified(Document xmlOld, Document xmlNew) throws MCRAccessException, IOException {
156         String oldData = new MCRJDOMContent(xmlOld).asString();
157         String newData = new MCRJDOMContent(xmlNew).asString();
158         if (!oldData.equals(newData)) {
159             MCRObject obj = new MCRObject(xmlNew);
160             MCRMetadataManager.update(obj);
161         }
162     }
163 
164     private static List<Element> getlevelElements(Document xml, String level) {
165         String path = MCRConfiguration2.getStringOrThrow(CFG_PREFIX_BASE + level);
166         XPathExpression<Element> xPath = XPathFactory.instance().compile(path, FE, null, NS);
167         return xPath.evaluate(xml);
168     }
169 
170     private static void add(Element base, String field, String value) throws JaxenException {
171         String path = MCRConfiguration2.getStringOrThrow(CFG_PREFIX + field + CFG_SUFFIX_ADD);
172         path = new MessageFormat(path, Locale.ROOT).format(new String[] { value });
173         new MCRNodeBuilder().buildNode(path, null, base);
174     }
175 
176     private static List<Element> find(Element base, String field, String value) {
177         String path = MCRConfiguration2.getStringOrThrow(CFG_PREFIX + field + CFG_SUFFIX_REMOVE);
178         path = new MessageFormat(path, Locale.ROOT).format(new String[] { value });
179         XPathExpression<Element> fPath = XPathFactory.instance().compile(path, FE, null, NS);
180         List<Element> selected = fPath.evaluate(base);
181         return selected;
182     }
183 }