001: /*
002: * Copyright 2004 Outerthought bvba and Schaubroeck nv
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package org.outerj.daisy.install;
017:
018: import java.io.BufferedReader;
019: import java.io.ByteArrayInputStream;
020: import java.io.File;
021: import java.io.FileReader;
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.io.InputStreamReader;
025: import java.util.ArrayList;
026: import java.util.List;
027: import java.util.regex.Matcher;
028: import java.util.regex.Pattern;
029:
030: import javax.xml.parsers.DocumentBuilder;
031: import javax.xml.parsers.DocumentBuilderFactory;
032:
033: import org.apache.commons.cli.CommandLine;
034: import org.apache.commons.cli.CommandLineParser;
035: import org.apache.commons.cli.HelpFormatter;
036: import org.apache.commons.cli.Option;
037: import org.apache.commons.cli.Options;
038: import org.apache.commons.cli.ParseException;
039: import org.apache.commons.cli.PosixParser;
040: import org.jaxen.JaxenException;
041: import org.jaxen.dom.DOMXPath;
042: import org.w3c.dom.Document;
043: import org.w3c.dom.Element;
044: import org.w3c.dom.NamedNodeMap;
045: import org.w3c.dom.Node;
046:
047: /**
048: * @author paul
049: *
050: */
051: public class XmlConfUpdater {
052: private File sourceFile;
053:
054: private File targetFile;
055:
056: private Document sourceDoc;
057:
058: private Document targetDoc;
059:
060: private List<UpdateAction> xPaths;
061:
062: private String LINE_FORMAT = "^((\\[(.*?):(.*?)\\])\\s)?(.*?)(\\s(<<[>!=]{2})\\s?(.*))?$";
063:
064: private boolean showDebug;
065:
066: /**
067: * @param args
068: * Accepted command line arguments '-t' '--target' '-s'
069: * '--source' '-x' '--xpaths' -h' '--help'
070: */
071: public static void main(String[] args) {
072: Options options = new Options();
073: Option sourceFileOption = new Option("s", "source", true,
074: "The source file that needs to be updated");
075: Option targetFileOption = new Option("t", "target", true,
076: "The target file that will serve as target");
077: Option xPaths = new Option("x", "xpaths", true,
078: "XPaths specifying which paths must be transfered from source to target");
079:
080: sourceFileOption.setRequired(true);
081: sourceFileOption.setArgName("source-file");
082: targetFileOption.setRequired(true);
083: targetFileOption.setArgName("target-file");
084: xPaths.setRequired(true);
085: xPaths.setArgName("xpaths-file");
086:
087: options.addOption(sourceFileOption);
088: options.addOption(targetFileOption);
089: options.addOption(xPaths);
090: options.addOption("d", "debug", false,
091: "Print debugging messages");
092: options.addOption("h", "help", false, "Show this message");
093:
094: CommandLineParser parser = new PosixParser();
095:
096: try {
097: CommandLine cmd = parser.parse(options, args);
098: try {
099: XmlConfUpdater updater = new XmlConfUpdater(cmd
100: .getOptionValue("s"), cmd.getOptionValue("t"),
101: cmd.getOptionValue("x"), cmd.hasOption("d"));
102: updater.update();
103: System.out
104: .println("Updated " + cmd.getOptionValue("t"));
105: } catch (Exception e) {
106: System.out.println("Could not update file "
107: + cmd.getOptionValue("t"));
108: e.printStackTrace();
109: System.exit(1);
110: }
111: } catch (ParseException e) {
112: HelpFormatter help = new HelpFormatter();
113: help.printHelp("XConfUpdater", options, true);
114: }
115: }
116:
117: /**
118: * Constucts an Updater object
119: *
120: * @param sourceFileName
121: * Source xml file from which items will be copied to the target
122: * xml file
123: * @param targetFileName
124: * Target xml file receiving items from the source file
125: * @param xpathFile
126: * File specifying XPaths that will be copied from source to
127: * target. Each entry is newline delimited.
128: * @throws Exception
129: */
130: public XmlConfUpdater(String sourceFileName, String targetFileName,
131: String xpathFile, boolean showDebug) throws Exception {
132: this .showDebug = showDebug;
133: sourceFile = new File(sourceFileName);
134: targetFile = new File(targetFileName);
135: sourceDoc = InstallHelper.parseFile(sourceFile);
136: targetDoc = InstallHelper.parseFile(targetFile);
137: xPaths = getXPaths(xpathFile);
138:
139: }
140:
141: /**
142: * Updates the target xml file with the source xml file. The items specified
143: * in the xPathFile will be copied from source to target. Existing tags in
144: * the target xml file will be overwritten.
145: *
146: * @throws Exception
147: */
148: public void update() throws Exception {
149: for (UpdateAction action : xPaths) {
150: try {
151: int status = action.execute();
152: if (showDebug) {
153: if (status == UpdateAction.ACTION_SUCCEEDED) {
154: System.out.println("Xpath OK : "
155: + action.getXPath().getRootExpr()
156: .getText());
157: } else {
158: System.out.println("skipping xpath "
159: + action.getXPath().getRootExpr()
160: .getText());
161: }
162: }
163: } catch (Exception e) {
164: e.printStackTrace();
165: System.err.println("Failed xpath"
166: + action.getXPath().getRootExpr().getText());
167: }
168: }
169: InstallHelper.saveDocument(targetFile, targetDoc);
170: }
171:
172: private List<UpdateAction> getXPaths(String fileName)
173: throws Exception {
174: List<UpdateAction> pathsInserts = new ArrayList<UpdateAction>();
175: BufferedReader br;
176: if (fileName.equals("-"))
177: br = new BufferedReader(new InputStreamReader(System.in));
178: else
179: br = new BufferedReader(new FileReader(fileName));
180:
181: try {
182: String line;
183: Pattern linePattern = Pattern.compile(LINE_FORMAT);
184: while ((line = br.readLine()) != null) {
185: Matcher lineMatcher = linePattern.matcher(line);
186: lineMatcher.find();
187: String nsPrefix = lineMatcher.group(3);
188: String ns = lineMatcher.group(4);
189: DOMXPath xpath = new DOMXPath(lineMatcher.group(5));
190: String commandString = lineMatcher.group(7);
191: String tag = lineMatcher.group(8);
192:
193: if (ns != null && nsPrefix != null)
194: xpath.addNamespace(nsPrefix, ns);
195:
196: UpdateAction xPathAction = UpdateAction
197: .createAction(sourceDoc, targetDoc, xpath, tag,
198: commandString);
199:
200: pathsInserts.add(xPathAction);
201: }
202:
203: } finally {
204: br.close();
205: }
206: return pathsInserts;
207: }
208:
209: private static abstract class UpdateAction {
210:
211: private static final String INSERT_SEPARATOR = "<<==";
212:
213: private static final String REPLACE_SEPARATOR = "<<>>";
214:
215: private static final String REMOVE_SEPARATOR = "<<!!";
216:
217: private static final String XPATH_REFERENCE_REGEX = "(?<!\\$)\\{.*?\\}";
218:
219: public static final int ACTION_SUCCEEDED = 0;
220:
221: public static final int ACTION_FAILED = 1;
222:
223: protected Document tagDoc;
224:
225: protected Document targetDoc;
226:
227: protected Document sourceDoc;
228:
229: protected DOMXPath xPath;
230:
231: private boolean showDebug = false;
232:
233: private UpdateAction(Document sourceDoc, Document targetDoc,
234: DOMXPath xPath, String tag) throws Exception {
235: this .sourceDoc = sourceDoc;
236: this .targetDoc = targetDoc;
237: this .xPath = xPath;
238: // Change the tag into a dom node
239: if (tag != null) {
240: DocumentBuilder documentBuilder = DocumentBuilderFactory
241: .newInstance().newDocumentBuilder();
242: InputStream is = new ByteArrayInputStream(tag
243: .getBytes());
244: tagDoc = documentBuilder.parse(is);
245: // replace { } thingies
246: translateXpathReferences(tagDoc.getDocumentElement(),
247: targetDoc.getDocumentElement());
248: }
249: }
250:
251: public static UpdateAction createAction(Document sourceDoc,
252: Document targetDoc, DOMXPath xpath, String tag,
253: String command) throws Exception {
254: UpdateAction action = null;
255: if (command == null)
256: action = new CopyAction(sourceDoc, targetDoc, xpath);
257: else if (command.equals(INSERT_SEPARATOR))
258: action = new InsertAction(sourceDoc, targetDoc, xpath,
259: tag);
260: else if (command.equals(REPLACE_SEPARATOR))
261: action = new ReplaceAction(sourceDoc, targetDoc, xpath,
262: tag);
263: else if (command.equals(REMOVE_SEPARATOR))
264: action = new RemoveAction(sourceDoc, targetDoc, xpath);
265:
266: return action;
267: }
268:
269: public abstract int execute() throws Exception;
270:
271: private void translateXpathReferences(Node newNode,
272: Node rootNode) throws Exception {
273: if (newNode.getChildNodes().getLength() > 0) {
274: for (int i = 0; i < newNode.getChildNodes().getLength(); i++)
275: translateXpathReferences(newNode.getChildNodes()
276: .item(i), rootNode);
277: }
278: Pattern pattern = Pattern.compile(XPATH_REFERENCE_REGEX);
279: if ((newNode.getNodeType() == Node.TEXT_NODE || newNode
280: .getNodeType() == Node.ATTRIBUTE_NODE)
281: && newNode.getNodeValue() != null) {
282:
283: String nodeValue = newNode.getNodeValue();
284: Matcher matcher = pattern.matcher(nodeValue);
285: while (matcher.find()) {
286: String xpath = matcher.group();
287: xpath = xpath.substring(1, xpath.length() - 1);
288: Node matchedNode = (Node) new DOMXPath(xpath)
289: .selectSingleNode(rootNode);
290: if (matchedNode != null) {
291: if (showDebug)
292: System.out
293: .println("Found node to be replaced at : "
294: + xpath
295: + " value : "
296: + matchedNode
297: .getNodeValue());
298: // substitute element or attribute text
299: if (matchedNode.getNodeType() == Node.TEXT_NODE
300: || matchedNode.getNodeType() == Node.ATTRIBUTE_NODE) {
301: nodeValue = matcher
302: .replaceFirst(matchedNode
303: .getNodeValue());
304: // append element
305: } else if (matchedNode.getNodeType() == Node.ELEMENT_NODE) {
306: newNode.getParentNode().appendChild(
307: newNode.getOwnerDocument()
308: .importNode(matchedNode,
309: true));
310: nodeValue = matcher.replaceFirst("");
311:
312: } else {
313: System.out
314: .println("I don't know what to do with that type of node :"
315: + matchedNode.getNodeType());
316: nodeValue = matcher.replaceFirst("");
317: }
318:
319: } else {
320: throw new IOException(
321: "This XPath could not be found "
322: + xpath);
323: }
324: matcher = pattern.matcher(nodeValue);
325: }
326: newNode.setNodeValue(nodeValue);
327:
328: } else if (newNode.getNodeType() == Node.ELEMENT_NODE) {
329: NamedNodeMap atts = newNode.getAttributes();
330: for (int i = 0; i < atts.getLength(); i++)
331: translateXpathReferences(atts.item(i), rootNode);
332: }
333: }
334:
335: public DOMXPath getXPath() {
336: return xPath;
337: }
338:
339: public void setXPath(DOMXPath path) {
340: xPath = path;
341: }
342:
343: public boolean isShowDebug() {
344: return showDebug;
345: }
346:
347: public void setShowDebug(boolean showDebug) {
348: this .showDebug = showDebug;
349: }
350: }
351:
352: private static class InsertAction extends UpdateAction {
353: public InsertAction(Document sourceDoc, Document targetDoc,
354: DOMXPath xPath, String tag) throws Exception {
355: super (sourceDoc, targetDoc, xPath, tag);
356: }
357:
358: public int execute() throws Exception {
359: int status = ACTION_SUCCEEDED;
360:
361: Node targetNode = (Node) xPath.selectSingleNode(targetDoc);
362: if (targetNode != null)
363: targetNode.appendChild(targetDoc.importNode(tagDoc
364: .getDocumentElement(), true));
365: else
366: status = ACTION_FAILED;
367:
368: return status;
369: }
370: }
371:
372: private static class ReplaceAction extends UpdateAction {
373: public ReplaceAction(Document sourceDoc, Document targetDoc,
374: DOMXPath xPath, String tag) throws Exception {
375: super (sourceDoc, targetDoc, xPath, tag);
376: }
377:
378: public int execute() throws Exception {
379: int status = ACTION_SUCCEEDED;
380:
381: Node targetNode = (Node) xPath.selectSingleNode(targetDoc);
382: Node newNode = targetDoc.importNode(tagDoc
383: .getDocumentElement(), true);
384: if (targetNode != null)
385: targetNode.getParentNode().replaceChild(newNode,
386: targetNode);
387: else
388: status = ACTION_FAILED;
389:
390: return status;
391: }
392: }
393:
394: private static class RemoveAction extends UpdateAction {
395: public RemoveAction(Document sourceDoc, Document targetDoc,
396: DOMXPath xPath) throws Exception {
397: super (sourceDoc, targetDoc, xPath, null);
398: }
399:
400: public int execute() throws Exception {
401: int status = ACTION_SUCCEEDED;
402: Node targetNode = (Node) xPath.selectSingleNode(targetDoc);
403: if (targetNode != null)
404: targetNode.getParentNode().removeChild(targetNode);
405: else
406: status = ACTION_FAILED;
407:
408: return status;
409: }
410: }
411:
412: private static class CopyAction extends UpdateAction {
413: public CopyAction(Document sourceDoc, Document targetDoc,
414: DOMXPath xPath) throws Exception {
415: super (sourceDoc, targetDoc, xPath, null);
416: }
417:
418: public int execute() throws Exception {
419: int status = ACTION_SUCCEEDED;
420: Element sourceElement = (Element) xPath
421: .selectSingleNode(sourceDoc);
422: Element targetElement = (Element) xPath
423: .selectSingleNode(targetDoc);
424: if (sourceElement != null) {
425: if (targetElement != null) {
426: // target element exists and will be replaced
427: targetElement.getParentNode().replaceChild(
428: targetDoc.importNode(sourceElement, true),
429: targetElement);
430: } else {
431: // target element does not exist so we'll climb the parental
432: // ladder to find the first existing item
433: do {
434: sourceElement = (Element) xPath
435: .selectSingleNode(sourceDoc);
436: xPath = getParentXpath(xPath);
437: targetElement = (Element) xPath
438: .selectSingleNode(targetDoc);
439: } while (targetElement == null);
440:
441: targetElement.appendChild(targetDoc.importNode(
442: sourceElement, true));
443: }
444: }
445: return status;
446: }
447:
448: private DOMXPath getParentXpath(DOMXPath path)
449: throws JaxenException {
450: String rootText = path.getRootExpr().getText();
451: return new DOMXPath(rootText.substring(0, rootText
452: .lastIndexOf("/")));
453: }
454: }
455: }
|