View Javadoc

1   /*
2    * Copyright 2008-2013 smartics, Kronseder & Reiner GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package de.smartics.maven.issues.cache;
17  
18  import java.io.Closeable;
19  import java.lang.reflect.Constructor;
20  import java.util.HashMap;
21  import java.util.Map;
22  
23  import javax.xml.namespace.QName;
24  import javax.xml.stream.XMLStreamConstants;
25  import javax.xml.stream.XMLStreamException;
26  import javax.xml.stream.XMLStreamReader;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.eclipse.mylyn.tasks.core.TaskRepository;
31  import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
32  import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper;
33  import org.eclipse.mylyn.tasks.core.data.TaskAttributeMetaData;
34  import org.eclipse.mylyn.tasks.core.data.TaskData;
35  
36  /**
37   * Reads a task from a given stream.
38   *
39   * @author <a href="mailto:robert.reiner@smartics.de">Robert Reiner</a>
40   * @version $Revision:591 $
41   */
42  public class XmlTaskReader implements Closeable
43  { // NOPMD
44    // ********************************* Fields *********************************
45  
46    // --- constants ------------------------------------------------------------
47  
48    // --- members --------------------------------------------------------------
49  
50    /**
51     * Reference to the logger for this class.
52     */
53    private final Log log = LogFactory.getLog(XmlTaskReader.class);
54  
55    /**
56     * The reader to read single tasks from.
57     */
58    protected final XMLStreamReader xmlReader;
59  
60    // ****************************** Initializer *******************************
61  
62    // ****************************** Constructors ******************************
63  
64    /**
65     * Default constructor.
66     *
67     * @param xmlReader the reader to read single tasks from.
68     * @throws NullPointerException if the <code>xmlWriter</code> is
69     *           <code>null</code>.
70     */
71    public XmlTaskReader(final XMLStreamReader xmlReader)
72      throws NullPointerException
73    {
74      if (xmlReader == null)
75      {
76        throw new NullPointerException("XML reader must not be 'null'.");
77      }
78      this.xmlReader = xmlReader;
79    }
80  
81    // ****************************** Inner Classes *****************************
82  
83    // ********************************* Methods ********************************
84  
85    // --- init -----------------------------------------------------------------
86  
87    // --- get&set --------------------------------------------------------------
88  
89    // --- business -------------------------------------------------------------
90  
91    /**
92     * Reads the task from the given XML stream.
93     *
94     * @return the task instance constructed from the information read from the
95     *         given stream, <code>null</code> if no element is on the stream.
96     * @throws XMLStreamException if the information cannot be read from the
97     *           stream.
98     */
99    public TaskData readTask() throws XMLStreamException
100   {
101     if (xmlReader.hasNext())
102     {
103       xmlReader.nextTag();
104       final QName qNameRoot = xmlReader.getName();
105       final String giRoot = qNameRoot.getLocalPart();
106       if (!TaskInfoKey.TASK_DATA.equals(giRoot))
107       {
108         throw new XMLStreamException("Expected '" + TaskInfoKey.TASK_DATA
109                                      + "' but encountered '" + giRoot + "'.");
110       }
111 
112       return readTaskData(giRoot);
113     }
114     return null;
115   }
116 
117   /**
118    * Reads the task data from the stream.
119    *
120    * @param giRoot the root element for logging.
121    * @return the read task data instance.
122    * @throws XMLStreamException if the task data cannot be read.
123    */
124   private TaskData readTaskData(final String giRoot) throws XMLStreamException
125   {
126     final TaskData taskData = createTaskData();
127 
128     if (xmlReader.hasNext())
129     {
130       xmlReader.nextTag();
131       final QName qName = xmlReader.getName();
132       final String gi = qName.getLocalPart();
133       if (TaskInfoKey.ATTRIBUTE.equals(gi))
134       {
135         provideAttribute(taskData.getRoot());
136       }
137       else
138       {
139         if (log.isDebugEnabled())
140         {
141           log.debug(createUnrecognizedElementMessage(giRoot, gi));
142         }
143       }
144     }
145     return taskData;
146   }
147 
148   /**
149    * Creates a log message for unrecognized elements.
150    *
151    * @param parentElementGi the element within which the <code>gi</code> was
152    *          encountered unexpectedly.
153    * @param nestedElementGi the generic identifier of the element within
154    *          <code>parentElement</code> that was not expected to be
155    *          encountered.
156    * @return a message to be logged.
157    */
158   private static String createUnrecognizedElementMessage(
159       final String parentElementGi, final String nestedElementGi)
160   {
161     return "Unrecognized tag within '" + parentElementGi + "': "
162            + nestedElementGi;
163   }
164 
165   /**
166    * Creates the task data instance containing basic information required to be
167    * passed to the constructor.
168    *
169    * @return the instantiated task data.
170    * @throws XMLStreamException if the instance cannot be created due to reading
171    *           problems or invalid information on the stream.
172    */
173   public TaskData createTaskData() throws XMLStreamException
174   {
175     final Map<String, String> properties = readElementAttributes();
176     final TaskData taskData = createTaskData(properties);
177     return taskData;
178   }
179 
180   /**
181    * Reads the attributes of the current element.
182    *
183    * @return the map of attributes read.
184    * @throws XMLStreamException if there is an error reading from the stream.
185    */
186   private Map<String, String> readElementAttributes() throws XMLStreamException
187   {
188     final int attributeCount = xmlReader.getAttributeCount();
189     final Map<String, String> attributes = new HashMap<String, String>();
190     for (int i = 0; i < attributeCount; i++)
191     {
192       final String name = xmlReader.getAttributeLocalName(i);
193       final String value = xmlReader.getAttributeValue(i);
194       attributes.put(name, value);
195     }
196     return attributes;
197   }
198 
199   /**
200    * Instantiates the task data from the properties given.
201    *
202    * @param properties the properties containing <code></code>
203    * @return the task data instance with basic information set.
204    * @throws XMLStreamException if the instance cannot be created for whatever
205    *           reason.
206    */
207   private TaskData createTaskData(final Map<String, String> properties)
208     throws XMLStreamException
209   {
210     try
211     {
212       final Constructor<TaskData> constructor =
213           TaskData.class.getConstructor(TaskAttributeMapper.class,
214               String.class, String.class, String.class);
215       final String taskId = properties.get(TaskInfoKey.TASK_ID);
216       final String repositoryUrl = properties.get(TaskInfoKey.REPOSITORY_URL);
217       final String connectorKind = properties.get(TaskInfoKey.CONNECTOR_KIND);
218 
219       final TaskRepository taskRepository =
220           new TaskRepository(connectorKind, repositoryUrl);
221       final TaskAttributeMapper mapper =
222           new TaskAttributeMapper(taskRepository);
223 
224       final TaskData taskData =
225           constructor.newInstance(mapper, connectorKind, repositoryUrl, taskId);
226       final String version = properties.get(TaskInfoKey.VERSION);
227       if (version != null)
228       {
229         taskData.setVersion(version);
230       }
231       return taskData;
232     }
233     catch (final Exception e)
234     {
235       throw new XMLStreamException("Cannot create task data with properties: "
236                                    + properties, e);
237     }
238   }
239 
240   /**
241    * Reads the attribute's information from the stream.
242    *
243    * @param attribute the attribute whose information is about to be read.
244    * @throws XMLStreamException if there is a problem reading the attribut's
245    *           information.
246    */
247   private void provideAttribute(final TaskAttribute attribute)
248     throws XMLStreamException
249   {
250     while (xmlReader.hasNext())
251     {
252       xmlReader.nextTag();
253       final QName qName = xmlReader.getName();
254       final String gi = qName.getLocalPart();
255       if (TaskInfoKey.ATTRIBUTE.equals(gi))
256       {
257         return;
258       }
259       else
260       {
261         readNextInformation(attribute, gi);
262       }
263     }
264   }
265 
266   /**
267    * Reads the next attribute information.
268    *
269    * @param attribute the attribute whose information is about to be read.
270    * @param gi selector to check which information is next on the stream.
271    * @throws XMLStreamException if there is a problem reading the attribut's
272    *           information.
273    */
274   private void readNextInformation(final TaskAttribute attribute,
275       final String gi) throws XMLStreamException
276   {
277     if (TaskInfoKey.ATTRIBUTES.equals(gi))
278     {
279       provideAttributes(attribute);
280     }
281     else if (TaskInfoKey.OPTIONS.equals(gi))
282     {
283       provideOptions(attribute);
284     }
285     else if (TaskInfoKey.META_DATA.equals(gi))
286     {
287       provideMetaData(attribute);
288     }
289     else if (TaskInfoKey.VALUES.equals(gi))
290     {
291       provideValues(attribute);
292     }
293     else
294     {
295       if (log.isDebugEnabled())
296       {
297         log.debug(createUnrecognizedElementMessage(TaskInfoKey.ATTRIBUTE, gi));
298       }
299     }
300   }
301 
302   /**
303    * Reads the attribute's meta data from the stream.
304    *
305    * @param attribute the attribute the meta data will be written to.
306    * @throws XMLStreamException if there is a problem reading the meta data.
307    */
308   private void provideMetaData(final TaskAttribute attribute)
309     throws XMLStreamException
310   {
311     final TaskAttributeMetaData metaData = attribute.getMetaData();
312     while (xmlReader.hasNext())
313     {
314       xmlReader.nextTag();
315       final QName qName = xmlReader.getName();
316       final String gi = qName.getLocalPart();
317       if (TaskInfoKey.META_DATA.equals(gi))
318       {
319         return;
320       }
321       else if (xmlReader.getEventType() == XMLStreamConstants.END_ELEMENT)
322       {
323         continue;
324       }
325       else
326       {
327         if (TaskInfoKey.ENTRY.equals(gi))
328         {
329           final String key = readSimpleElement(TaskInfoKey.KEY);
330           final String value = readSimpleElement(TaskInfoKey.VALUE);
331           metaData.putValue(key, value);
332           xmlReader.nextTag();
333         }
334         else
335         {
336           if (log.isDebugEnabled())
337           {
338             log.debug(createUnrecognizedElementMessage(TaskInfoKey.META_DATA,
339                 gi));
340           }
341         }
342       }
343     }
344   }
345 
346   /**
347    * Reads an element that contains only text.
348    *
349    * @param expectedGi the expected generic identifier (GI) of the element.
350    * @return the text content of the element.
351    * @throws XMLStreamException if the GI or text content cannot be read.
352    */
353   private String readSimpleElement(final String expectedGi)
354     throws XMLStreamException
355   {
356     xmlReader.nextTag();
357     final QName qName = xmlReader.getName();
358     final String gi = qName.getLocalPart();
359     if (expectedGi.equals(gi))
360     {
361       xmlReader.next();
362       final String value = readCharacters();
363       return value;
364     }
365     else
366     {
367       throw new XMLStreamException("Expected '" + expectedGi + "' but found '"
368                                    + gi + "'.");
369     }
370   }
371 
372   /**
373    * Reads the attribute's options from the stream.
374    *
375    * @param attribute the attribute the options will be written to.
376    * @throws XMLStreamException if there is a problem reading the options.
377    */
378   private void provideOptions(final TaskAttribute attribute)
379     throws XMLStreamException
380   {
381     while (xmlReader.hasNext())
382     {
383       xmlReader.nextTag();
384       final QName qName = xmlReader.getName();
385       final String gi = qName.getLocalPart();
386       if (TaskInfoKey.OPTIONS.equals(gi))
387       {
388         return;
389       }
390       else if (xmlReader.getEventType() == XMLStreamConstants.END_ELEMENT)
391       {
392         continue;
393       }
394       else
395       {
396         if (TaskInfoKey.ENTRY.equals(gi))
397         {
398           final String key = readSimpleElement(TaskInfoKey.KEY);
399           final String value = readSimpleElement(TaskInfoKey.VALUE);
400           attribute.putOption(key, value);
401           xmlReader.nextTag();
402         }
403         else
404         {
405           if (log.isDebugEnabled())
406           {
407             log.debug(createUnrecognizedElementMessage(TaskInfoKey.OPTIONS, gi));
408           }
409         }
410       }
411     }
412   }
413 
414   /**
415    * Adds values to the attribute.
416    *
417    * @param attribute the attribute whose values are about to be read.
418    * @throws XMLStreamException if there is a problem reading the attribute's
419    *           values.
420    */
421   private void provideValues(final TaskAttribute attribute)
422     throws XMLStreamException
423   {
424     xmlReader.nextTag();
425     while (xmlReader.hasNext())
426     {
427       final QName qName = xmlReader.getName();
428       final String gi = qName.getLocalPart();
429       if (TaskInfoKey.VALUES.equals(gi))
430       {
431         return;
432       }
433       else if (xmlReader.getEventType() == XMLStreamConstants.END_ELEMENT)
434       {
435         xmlReader.nextTag();
436         continue;
437       }
438       else if (TaskInfoKey.VALUE.equals(gi))
439       {
440         xmlReader.next();
441         final String value = readCharacters();
442         attribute.addValue(value);
443       }
444     }
445   }
446 
447   /**
448    * Reads all character elements until any other element is encountered. The
449    * content of all elements is returned as one string.
450    *
451    * @return the string containing all characters of subsequent
452    *         {@link XMLStreamConstants#CHARACTER characters}.
453    * @throws XMLStreamException if the characters cannot be read.
454    */
455   private String readCharacters() throws XMLStreamException
456   {
457     final StringBuilder buffer = new StringBuilder();
458     while (xmlReader.getEventType() == XMLStreamConstants.CHARACTERS)
459     {
460       buffer.append(xmlReader.getText());
461       xmlReader.next();
462     }
463     return buffer.toString();
464   }
465 
466   /**
467    * Provides the sub attributes for the given attribute.
468    *
469    * @param attribute the parent attribute to the attributes being read.
470    * @throws XMLStreamException if there is a problem reading the attributes.
471    */
472   private void provideAttributes(final TaskAttribute attribute)
473     throws XMLStreamException
474   {
475     while (xmlReader.hasNext())
476     {
477       xmlReader.nextTag();
478       final QName qName = xmlReader.getName();
479       final String gi = qName.getLocalPart();
480       if (TaskInfoKey.ATTRIBUTES.equals(gi))
481       {
482         return;
483       }
484       else
485       {
486         final String attributeId = getAttribute(TaskInfoKey.ATTRIBUTE_ID);
487         final TaskAttribute subAttribute =
488             new TaskAttribute(attribute, attributeId); // NOPMD
489         provideAttribute(subAttribute);
490       }
491     }
492 
493   }
494 
495   /**
496    * A convenience method if just one single attribute of an element is
497    * requested.
498    *
499    * @param name the name of the attribute to return.
500    * @return the value of the attribute with the given <code>name</code>.
501    * @throws XMLStreamException if there is a problem reading the current
502    *           element's attributes.
503    */
504   private String getAttribute(final String name) throws XMLStreamException
505   {
506     final Map<String, String> attributes = readElementAttributes();
507     return attributes.get(name);
508   }
509 
510   /**
511    * {@inheritDoc}
512    * <p>
513    * The stream is closed quietly, logging any failure at error level.
514    * </p>
515    *
516    * @see java.io.Closeable#close()
517    */
518   public void close()
519   {
520     try
521     {
522       this.xmlReader.close();
523     }
524     catch (final XMLStreamException e)
525     {
526       if (log.isErrorEnabled())
527       {
528         log.error("Cannot close XML stream of task reader.");
529       }
530     }
531   }
532 
533   // --- object basics --------------------------------------------------------
534 
535 }