View Javadoc

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