View Javadoc

1   /*
2    * Copyright 2012-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.properties.spi.core.metadata.projectdoc;
17  
18  import static de.smartics.util.lang.StaticAnalysis.UNCHECKED;
19  
20  import java.io.BufferedInputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.util.List;
24  import java.util.Locale;
25  
26  import javax.annotation.CheckForNull;
27  
28  import org.apache.commons.io.IOUtils;
29  import org.jdom.Document;
30  import org.jdom.Element;
31  import org.jdom.JDOMException;
32  import org.jdom.Namespace;
33  import org.jdom.input.SAXBuilder;
34  import org.jdom.output.XMLOutputter;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  import de.smartics.properties.api.core.domain.DocumentMetaData;
39  import de.smartics.properties.api.core.domain.ProjectdocMetaData;
40  import de.smartics.properties.api.core.domain.PropertiesContext;
41  import de.smartics.properties.api.core.domain.PropertyDescriptor;
42  import de.smartics.properties.spi.core.metadata.projectdoc.ProjectdocAnnotationCollector.Defaults;
43  import de.smartics.util.lang.Arg;
44  
45  /**
46   * Parses projectdoc information from property XML descriptors.
47   */
48  public abstract class ProjectdocMetaDataParser
49  {
50    // ********************************* Fields *********************************
51  
52    // --- constants ------------------------------------------------------------
53  
54    /**
55     * Reference to the logger for this class.
56     */
57    private static final Logger LOG = LoggerFactory
58        .getLogger(ProjectdocMetaDataParser.class);
59  
60    // --- members --------------------------------------------------------------
61  
62    /**
63     * The properties context to fetch information from the META-INF folder.
64     */
65    protected final PropertiesContext context;
66  
67    /**
68     * The metadata defaults to use.
69     */
70    protected final Defaults defaults;
71  
72    // ****************************** Initializer *******************************
73  
74    // ****************************** Constructors ******************************
75  
76    /**
77     * Convenience constructor without defaults.
78     *
79     * @param context the properties context to fetch information from the
80     *          META-INF folder.
81     * @throws NullPointerException if {@code context} is <code>null</code>.
82     */
83    protected ProjectdocMetaDataParser(final PropertiesContext context)
84      throws NullPointerException
85    {
86      this(context, null);
87    }
88  
89    /**
90     * Default constructor.
91     *
92     * @param context the properties context to fetch information from the
93     *          META-INF folder.
94     * @param defaults the metadata defaults to use.
95     * @throws NullPointerException if {@code context} is <code>null</code>.
96     */
97    protected ProjectdocMetaDataParser(final PropertiesContext context,
98        final Defaults defaults) throws NullPointerException
99    {
100     this.context = Arg.checkNotNull("context", context);
101     this.defaults = defaults;
102   }
103 
104   // ****************************** Inner Classes *****************************
105 
106   /**
107    * Provide access to information relevant for parsing and storing the parsed
108    * information.
109    */
110   public final class ParserContext
111   {
112 
113     // ******************************** Fields ********************************
114 
115     // --- constants ----------------------------------------------------------
116 
117     // --- members ------------------------------------------------------------
118 
119     /**
120      * The descriptor whose comments are requested to be parsed.
121      */
122     private final PropertyDescriptor descriptor;
123 
124     /**
125      * The locale to select the comments.
126      */
127     private final Locale locale;
128 
129     /**
130      * The identifier of the document.
131      */
132     private final String systemId;
133 
134     /**
135      * The parsed document to extract information from.
136      */
137     private final Document document;
138 
139     /**
140      * The container where the parsed information is put to. You may safely cast
141      * this instance to the type that has been passed to
142      * {@link #parseBase(PropertyDescriptor, Locale, ProjectdocMetaData)}.
143      */
144     private final ProjectdocMetaData metaData;
145 
146     // ***************************** Initializer ******************************
147 
148     // ***************************** Constructors *****************************
149 
150     private ParserContext(final PropertyDescriptor descriptor,
151         final Locale locale, final String systemId, final Document document,
152         final ProjectdocMetaData metaData)
153     {
154       this.descriptor = descriptor;
155       this.locale = locale;
156       this.systemId = systemId;
157       this.document = document;
158       this.metaData = metaData;
159     }
160 
161     // ***************************** Inner Classes ****************************
162 
163     // ******************************** Methods *******************************
164 
165     // --- init ---------------------------------------------------------------
166 
167     // --- get&set ------------------------------------------------------------
168 
169     /**
170      * Returns the descriptor whose comments are requested to be parsed.
171      *
172      * @return the descriptor whose comments are requested to be parsed.
173      */
174     public PropertyDescriptor getDescriptor()
175     {
176       return descriptor;
177     }
178 
179     /**
180      * Returns the locale to select the comments.
181      *
182      * @return the locale to select the comments.
183      */
184     public Locale getLocale()
185     {
186       return locale;
187     }
188 
189     /**
190      * Returns the identifier of the document.
191      *
192      * @return the identifier of the document.
193      */
194     public String getSystemId()
195     {
196       return systemId;
197     }
198 
199     /**
200      * Returns the parsed document to extract information from.
201      *
202      * @return the parsed document to extract information from.
203      */
204     public Document getDocument()
205     {
206       return document;
207     }
208 
209     /**
210      * Returns the container where the parsed information is put to. You may
211      * safely cast this instance to the type that has been passed to
212      * {@link #parseBase(PropertyDescriptor,Locale,ProjectdocMetaData)}.
213      *
214      * @return the container where the parsed information is put to.
215      */
216     public ProjectdocMetaData getMetaData()
217     {
218       return metaData;
219     }
220 
221     // --- business -----------------------------------------------------------
222 
223     // --- object basics ------------------------------------------------------
224   }
225 
226   // ********************************* Methods ********************************
227 
228   // --- init -----------------------------------------------------------------
229 
230   // --- get&set --------------------------------------------------------------
231 
232   // --- business -------------------------------------------------------------
233 
234   /**
235    * Parses the comments for the given descriptor.
236    * <p>
237    * This method is overridden by subclasses to return their specific
238    * implementation of the {@link DocumentMetaData}.
239    * </p>
240    *
241    * @param descriptor the descriptor whose comments are requested to be parsed.
242    * @param locale the locale to select the comments.
243    * @return the comments for the given locale or the default comments, if the
244    *         locale is not supported. May be <code>null</code> if no information
245    *         is provided.
246    */
247   @CheckForNull
248   public ProjectdocMetaData parse(final PropertyDescriptor descriptor,
249       final Locale locale)
250   {
251     try
252     {
253       final ProjectdocMetaData metaData = new ProjectdocMetaData();
254       parseBase(descriptor, locale, metaData);
255       return metaData;
256     }
257     catch (final MetaDataException e)
258     {
259       LOG.warn("Cannot parse meta data for property descriptor '{}': {}",
260           descriptor, e);
261       return null;
262     }
263   }
264 
265   /**
266    * Parses the comments for the given descriptor.
267    *
268    * @param descriptor the descriptor whose comments are requested to be parsed.
269    * @param locale the locale to select the comments.
270    * @param metaData the container where the parsed information is put to.
271    * @throws MetaDataException if the meta data cannot be read.
272    */
273   protected final void parseBase(final PropertyDescriptor descriptor,
274       final Locale locale, final ProjectdocMetaData metaData)
275     throws MetaDataException
276   {
277     final String path = calcPath(descriptor, locale);
278 
279     final ClassLoader loader = descriptor.getDeclaringType().getClassLoader();
280 
281     InputStream input = null;
282     try
283     {
284       final InputStream resource = load(path, loader);
285       if (resource != null)
286       {
287         input = new BufferedInputStream(resource);
288         parse(descriptor, path, locale, input, metaData);
289       }
290       else
291       {
292         throw new MetaDataException(path);
293       }
294     }
295     finally
296     {
297       IOUtils.closeQuietly(input);
298     }
299   }
300 
301   /**
302    * Calculates the path to a resource to be parsed.
303    *
304    * @param descriptor the descriptor to the resource.
305    * @param locale the requested locale.
306    * @return the path to the resource.
307    */
308   protected abstract String calcPath(final PropertyDescriptor descriptor,
309       final Locale locale);
310 
311   private InputStream load(final String path, final ClassLoader loader)
312   {
313     InputStream input = loader.getResourceAsStream(path);
314     if (input == null)
315     {
316       input = loader.getResourceAsStream('/' + path);
317     }
318     return input;
319   }
320 
321   private DocumentMetaData parse(final PropertyDescriptor descriptor,
322       final String systemId, final Locale locale, final InputStream input,
323       final ProjectdocMetaData metaData) throws MetaDataException
324   {
325     try
326     {
327       final SAXBuilder parser = new SAXBuilder();
328       final Document document = parser.build(input, systemId);
329 
330       final Element rootNode = document.getRootElement();
331       addIdInfo(metaData, rootNode);
332       addFiling(metaData, rootNode);
333       addDescription(metaData, rootNode);
334 
335       final ParserContext context =
336           new ParserContext(descriptor, locale, systemId, document, metaData);
337       parseAdditional(context);
338 
339       return metaData;
340     }
341     catch (final JDOMException e)
342     {
343       throw new MetaDataException(systemId, e);
344     }
345     catch (final IOException e)
346     {
347       throw new MetaDataException(systemId, e);
348     }
349   }
350 
351   /**
352    * The hook that allows to add further information.
353    *
354    * @param context to provide access to information relevant for parsing and
355    *          storing the parsed information.
356    * @throws MetaDataException if the meta data cannot be read.
357    */
358   protected void parseAdditional(final ParserContext context)
359     throws MetaDataException
360   {
361   }
362 
363   private void addIdInfo(final ProjectdocMetaData metaData,
364       final Element rootNode)
365   {
366     if (defaults != null)
367     {
368       metaData.setName(defaults.getName());
369       metaData.setSpace(defaults.getSpace());
370       metaData.setTitle(defaults.getTitle());
371     }
372 
373     final Element identification = rootNode.getChild("identification", getNs());
374     if (identification != null)
375     {
376       final String space =
377           identification.getChildTextNormalize("space", getNs());
378       metaData.setSpace(space);
379       final String title =
380           identification.getChildTextNormalize("title", getNs());
381       metaData.setTitle(title);
382     }
383 
384     final String name = rootNode.getChildTextNormalize("name", getNs());
385     metaData.setName(name);
386   }
387 
388   private void addFiling(final ProjectdocMetaData metaData,
389       final Element rootNode)
390   {
391     final Element filing = rootNode.getChild("filing", getNs());
392     if (filing != null)
393     {
394       addParents(metaData, filing);
395       addCategories(metaData, filing);
396       addTags(metaData, filing);
397       setSortKey(metaData, filing);
398     }
399   }
400 
401   @SuppressWarnings(UNCHECKED)
402   private void addParents(final ProjectdocMetaData metaData,
403       final Element filing)
404   {
405     final Element parentsElement = filing.getChild("parents", getNs());
406     if (parentsElement != null)
407     {
408       final List<Element> parents =
409           parentsElement.getChildren("parent", getNs());
410       for (final Element parent : parents)
411       {
412         final String parentName = parent.getTextNormalize();
413         metaData.addParent(parentName);
414       }
415     }
416   }
417 
418   @SuppressWarnings(UNCHECKED)
419   private void addCategories(final ProjectdocMetaData metaData,
420       final Element filing)
421   {
422     final Element categoriesElement = filing.getChild("categories", getNs());
423     if (categoriesElement != null)
424     {
425       final List<Element> categories =
426           categoriesElement.getChildren("category", getNs());
427       for (final Element category : categories)
428       {
429         final String categoryName = category.getTextNormalize();
430         metaData.addCategory(categoryName);
431       }
432     }
433   }
434 
435   @SuppressWarnings(UNCHECKED)
436   private void addTags(final ProjectdocMetaData metaData, final Element filing)
437   {
438     final Element tagsElement = filing.getChild("tags", getNs());
439     if (tagsElement != null)
440     {
441       final List<Element> tags = tagsElement.getChildren("tag", getNs());
442       for (final Element tag : tags)
443       {
444         final String tagName = tag.getTextNormalize();
445         metaData.addTag(tagName);
446       }
447     }
448   }
449 
450   private void setSortKey(final ProjectdocMetaData metaData,
451       final Element filing)
452   {
453     final String sortKey = filing.getChildTextNormalize("sortKey", getNs());
454     metaData.setSortKey(sortKey);
455   }
456 
457   @SuppressWarnings(UNCHECKED)
458   private void addDescription(final ProjectdocMetaData metaData,
459       final Element rootNode)
460   {
461     final Element description = rootNode.getChild("description", getNs());
462     if (description != null)
463     {
464       final Element audienceElement = description.getChild("audience", getNs());
465       if (audienceElement != null)
466       {
467         final List<Element> members =
468             audienceElement.getChildren("member", getNs());
469         for (final Element member : members)
470         {
471           final String memberName = member.getTextNormalize();
472           metaData.addAudience(memberName);
473         }
474       }
475 
476       final Element shortDescriptionElement =
477           description.getChild("shortDescription", getNs());
478       final String shortDescription = toString(shortDescriptionElement);
479       metaData.setShortDescription(shortDescription);
480 
481       final Element summaryElement = description.getChild("summary", getNs());
482       final String summary = toString(summaryElement);
483       metaData.setSummary(summary);
484 
485       final Element notesElement = description.getChild("notes", getNs());
486       final String notes = toString(notesElement);
487       metaData.addNote(notes);
488     }
489   }
490 
491   /**
492    * Returns the namespace of the elements parsed by this parser.
493    *
494    * @return the namespace of the elements parsed by this parser.
495    */
496   protected abstract Namespace getNs();
497 
498   private static String toString(final Element element)
499   {
500     final XMLOutputter outp = new XMLOutputter();
501     final StringBuilder buffer = new StringBuilder(1024);
502 
503     final List<?> children = element.getContent();
504     final String string = outp.outputString(children);
505     buffer.append(string);
506 
507     return buffer.toString().trim();
508   }
509 
510   // --- object basics --------------------------------------------------------
511 
512 }