1/*******************************************************************************
2 * Copyright (c) 2005, 2006 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *     IBM Corporation - initial API and implementation
10 *******************************************************************************/
11package org.eclipse.releng.generators.rss;
12
13import java.io.File;
14import java.io.FileNotFoundException;
15import java.io.IOException;
16import java.io.PrintStream;
17import java.util.Date;
18
19import javax.xml.parsers.DocumentBuilder;
20import javax.xml.parsers.DocumentBuilderFactory;
21import javax.xml.parsers.ParserConfigurationException;
22import javax.xml.transform.Transformer;
23import javax.xml.transform.TransformerException;
24import javax.xml.transform.dom.DOMSource;
25import javax.xml.transform.stream.StreamResult;
26import javax.xml.xpath.XPath;
27import javax.xml.xpath.XPathConstants;
28import javax.xml.xpath.XPathExpressionException;
29import javax.xml.xpath.XPathFactory;
30
31import org.apache.tools.ant.BuildException;
32import org.apache.tools.ant.Task;
33import org.apache.tools.ant.util.DateUtils;
34import org.w3c.dom.Attr;
35import org.w3c.dom.Document;
36import org.w3c.dom.Element;
37import org.w3c.dom.Node;
38import org.w3c.dom.NodeList;
39import org.w3c.dom.Text;
40import org.xml.sax.SAXException;
41
42import org.eclipse.releng.util.rss.Messages;
43import org.eclipse.releng.util.rss.RSSFeedUtil;
44
45/**
46 * Parameters:
47 *   debug - more output to console - eg., 0|1|2
48 *
49 *   file - path to the XML file that will be read - eg., /path/to/file.to.read.xml
50 *   xpath - xpath string representing the object to modify
51 *   replacement - string to use as replacement
52 *
53 * @author nickb
54 *
55 */
56public class RSSFeedUpdateEntryTask extends Task {
57
58  private int debug = 0;
59
60  private static final String now = getTimestamp();
61
62  private static final XPath xp = XPathFactory.newInstance().newXPath();
63
64  private static final String NS = ""; //$NON-NLS-1$
65  private static final String SEP = "----"; //$NON-NLS-1$
66  private static final String SP = " "; //$NON-NLS-1$
67
68  //required fields
69  private File file;
70
71  private String xpath;
72  private String replacement;
73
74  private Transformer transformer = null;
75
76  private boolean isNodeFound = false;
77  private boolean isNodeChanged = false;
78  private Node foundNode = null;
79
80  //optional
81  public void setDebug(int debug) { this.debug = debug; }
82
83  //required fields
84  public void setFile(String file) {
85    if (isNullString(file))
86    { System.err.println(Messages.getString("RSSFeedCommon.FileError")); } //$NON-NLS-1$
87    else
88    { this.file = new File(file); }
89  }
90  public void setXpath(String xpath) {
91    if (isNullString(xpath))
92    { System.err.println(Messages.getString("RSSFeedCommon.XpathError")); } //$NON-NLS-1$
93    else
94    { this.xpath = xpath; }
95  }
96
97  //optional - if null, display value found instead of changing it - see RSSFeedGetPropertyTask
98  public void setReplacement(String replacement) { this.replacement = replacement; }
99
100  // The method executing the task
101  public void execute() throws BuildException {
102    if (debug>0) {
103      System.out.println(Messages.getString("RSSFeedUpdateEntryTask.SearchingFor") + SP + xpath + (!isNullString(replacement)?", " + Messages.getString("RSSFeedUpdateEntryTask.ReplacingWith") + " '" + replacement + "'":NS)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
104    }
105    updateFeedXML(file); // load previous
106  }
107
108  //$ANALYSIS-IGNORE codereview.java.rules.exceptions.RuleExceptionsSpecificExceptions
109  private void updateFeedXML(File file){
110    if (file.exists()) {
111      DocumentBuilderFactory documentBuilderFactory=DocumentBuilderFactory.newInstance();
112      documentBuilderFactory.setNamespaceAware(true);
113      DocumentBuilder documentBuilder=null;
114      try {
115        documentBuilder=documentBuilderFactory.newDocumentBuilder();
116      }
117      catch (ParserConfigurationException e) {
118        e.printStackTrace();
119      }
120      Document document=null;
121      try {
122        document=documentBuilder.parse(file);
123      }
124      catch (SAXException e) {
125        e.printStackTrace();
126      }
127      catch (IOException e) {
128        e.printStackTrace();
129      }
130
131      try {
132        transformer = RSSFeedAddEntryTask.createTransformer("UTF-8"); //$NON-NLS-1$
133      } catch (TransformerException e) {
134        e.printStackTrace();
135      }
136
137      if (!isNullString(replacement)) {
138        setEntryNodeUpdate(document.getDocumentElement());
139      }
140      Node newNode=findAndReplace(document);
141      if (debug > 1 && newNode != null) {
142        try {
143          System.out.println(SEP);
144          transformer.transform(new DOMSource(newNode),new StreamResult(System.out));
145          System.out.println(SEP);
146        }
147        catch (TransformerException e) {
148          e.printStackTrace();
149        }
150      }
151      if (!isNullString(replacement) && newNode != null) {
152        try {
153          transformer.transform(new DOMSource(document),new StreamResult(new PrintStream(file)));
154        }
155        catch (FileNotFoundException e) {
156          e.printStackTrace();
157        }
158        catch (TransformerException e) {
159          e.printStackTrace();
160        }
161      }
162    }
163    else {
164      System.out.println(Messages.getString("RSSFeedCommon.RSSFeedFile") + SP + file.toString()+ " "+ Messages.getString("RSSFeedUpdateEntryTask.DoesNotExist")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
165    }
166  }
167
168  // get/set the desired node
169  public Node getFoundNode()
170  {
171    return this.foundNode;
172  }
173  private void setFoundNode(Node foundNode)
174  {
175    this.foundNode = foundNode;
176  }
177
178  // has the desired node been found?
179  public boolean getNodeFound()
180  {
181    return this.isNodeFound;
182  }
183  private void setNodeFound(boolean isNodeFound)
184  {
185    this.isNodeFound = isNodeFound;
186  }
187
188  // has the desired node been changed?
189  public boolean getNodeChanged()
190  {
191    return this.isNodeChanged;
192  }
193  private void setNodeChanged(boolean isNodeChanged)
194  {
195    this.isNodeChanged = isNodeChanged;
196  }
197
198  /**
199   * Modify an entry:
200   *
201   *   <entry>
202   *      <title/>
203   *      <link href=""/>
204   *      <id/>
205   *      <updated/>
206   *      <summary>
207   *       ...
208   *     </summary>
209   *   </entry>
210   */
211  private Node findAndReplace(Document document) {
212    Node parentEntryNode = null;
213    Node aNode = null;
214    if (debug==0) { System.out.print(xpath + (isNullString(replacement)?" = ":" :: ")); } //$NON-NLS-1$ //$NON-NLS-2$
215    NodeList nodelist = getNodeList(document, xpath);
216    // Process the elements in the nodelist
217    if (nodelist != null && nodelist.getLength()>0) {
218      for (int i=0; i<nodelist.getLength(); i++) {
219        Node node = (Node)nodelist.item(i);
220        switch (node.getNodeType())
221        {
222          case Node.ATTRIBUTE_NODE :
223            aNode = (Attr)nodelist.item(i);
224            if (debug>0) { System.out.print(Messages.getString("RSSFeedUpdateEntryTask.DebugFoundAttribute")); }  //$NON-NLS-1$
225            break;
226
227          case Node.ELEMENT_NODE :
228            aNode = (Element)nodelist.item(i);
229            if (debug>0) { System.out.print(Messages.getString("RSSFeedUpdateEntryTask.DebugFoundElement")); } //$NON-NLS-1$
230            break;
231
232          case Node.TEXT_NODE :
233            aNode = (Text)nodelist.item(i);
234            if (debug>0) { System.out.print(Messages.getString("RSSFeedUpdateEntryTask.DebugFoundText")); } //$NON-NLS-1$
235            break;
236
237          default:
238            aNode = null;
239          break;
240        }
241        if (aNode != null) {
242          setFoundNode(aNode);
243          setNodeFound(true);
244          System.out.print((debug>0?aNode.getNodeName() + " = ":NS) + aNode.getNodeValue()); //$NON-NLS-1$ //$NON-NLS-2$
245          if (!isNullString(replacement)) { aNode.setTextContent(replacement); }
246          System.out.println(isNullString(replacement)?NS:" => " + replacement); //$NON-NLS-1$ //$NON-NLS-2$
247          if (debug>0) {
248            try
249            {
250              // write to console
251              System.out.println(SEP); //$NON-NLS-1$
252              transformer.transform(new DOMSource(getParentNode(document,aNode,null,NS)), new StreamResult(System.out));  //$NON-NLS-1$
253              System.out.println(SEP); //$NON-NLS-1$
254            }
255            catch (TransformerException e)
256            {
257              e.printStackTrace();
258            }
259          }
260          if (!isNullString(replacement)) {
261            parentEntryNode = getParentNode(document, aNode, "entry", NS); //$NON-NLS-1$ //$NON-NLS-2$
262            setEntryNodeUpdate(parentEntryNode);
263          }
264        }
265      }
266    } else {
267      System.out.println(Messages.getString("RSSFeedUpdateEntryTask.XpathNodeNotFound")); //$NON-NLS-1$
268    }
269    return parentEntryNode;
270  }
271
272  private Node getParentNode(Document document, Node nodeIn, String target, String indent)
273  {
274    Node node = nodeIn;
275    if (node.getNodeType() != Node.ELEMENT_NODE) {
276      if (debug>1) { System.out.println(indent + Messages.getString("RSSFeedUpdateEntryTask.DebugGotATNode") + node.getNodeName()); } //$NON-NLS-1$
277      // get the element for the attrib/text node
278      NodeList nodelist = getNodeList(document, xpath.substring(0, xpath.lastIndexOf("/")));
279      if (nodelist !=null && nodelist.getLength()>0)
280      {
281        for (int i=0; i<nodelist.getLength(); i++) {
282          node = (Node)nodelist.item(i);
283          break;
284        }
285      }
286    }
287    if (debug>1) { System.out.println(indent + Messages.getString("RSSFeedUpdateEntryTask.DebugGotENode") + node.getNodeName() + " (" + node.getNodeType() + ")"); } //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
288    if (!isNullString(target) && !node.getNodeName().equals(target))
289    {
290      node = getParentNode(document, node.getParentNode(), target, indent + "_ "); //$NON-NLS-1$
291    }
292    return node;
293  }
294
295  private NodeList getNodeList(Document document, String xpath)
296  {
297    NodeList nodelist = null;
298    try
299    {
300      xp.reset();
301      Object o = xp.evaluate(xpath, document, XPathConstants.NODESET);
302      if (o instanceof NodeList)
303      {
304        nodelist = (NodeList)o;
305      }
306    }
307    catch (XPathExpressionException e)
308    {
309      e.printStackTrace();
310    }
311    return nodelist;
312  }
313
314  //$ANALYSIS-IGNORE codereview.java.rules.exceptions.RuleExceptionsSpecificExceptions
315  private void setEntryNodeUpdate(Node parentEntryNode){
316    for (Node child=parentEntryNode.getFirstChild(); child != null; child=child.getNextSibling()) {
317      if ("updated".equals(child.getLocalName())) { //$NON-NLS-1$
318        if (debug > 0) {
319          System.out.println(Messages.getString("RSSFeedCommon.Set") + " <" + child.getLocalName()+ ">"+ now+ "</"+ child.getLocalName()+ ">"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
320        }
321        ((Element)child).setTextContent(now);
322        setNodeChanged(true);
323        break;
324      }
325    }
326  }
327
328
329  private static String getTimestamp() { // eg., 2006-04-10T20:40:08Z
330    return DateUtils.format(new Date(), DateUtils.ISO8601_DATETIME_PATTERN) + "Z";  //$NON-NLS-1$
331  }
332
333  private static boolean isNullString(String str)
334  {
335    return RSSFeedUtil.isNullString(str);
336  }
337
338}