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.api.core.domain;
17  
18  import java.io.Serializable;
19  import java.net.URL;
20  import java.util.ArrayList;
21  import java.util.List;
22  import java.util.Locale;
23  
24  import javax.annotation.CheckForNull;
25  import javax.annotation.concurrent.ThreadSafe;
26  
27  import org.apache.commons.lang.LocaleUtils;
28  import org.apache.commons.lang.StringUtils;
29  import org.apache.commons.lang.builder.ToStringBuilder;
30  
31  import de.smartics.properties.api.core.context.alias.AliasTraverser;
32  import de.smartics.properties.api.core.context.alias.DuplicateAliasException;
33  import de.smartics.properties.api.core.context.alias.PropertyAliasMapping;
34  import de.smartics.properties.api.core.context.alias.UnknownAliasException;
35  import de.smartics.util.lang.Arg;
36  import de.smartics.util.lang.BlankArgumentException;
37  
38  /**
39   * Defines the configuration for smartics properties configuration.
40   * <p>
41   * A context provides information for all its properties. Not all properties of
42   * an application may refer to the same context.
43   * </p>
44   *
45   * @impl For testing purposes please refer to
46   *       <code>help.de.smartics.properties.core.PropertiesContextBuilder</code>.
47   */
48  @ThreadSafe
49  public final class PropertiesContext implements Serializable
50  { // NOPMD
51    // ********************************* Fields *********************************
52  
53    // --- constants ------------------------------------------------------------
54  
55    /**
56     * The class version identifier.
57     */
58    private static final long serialVersionUID = 1L;
59  
60    /**
61     * The path to the folder within <code>META-INF</code> where properties
62     * resources are located.
63     * <p>
64     * The value of this constant is {@value}.
65     * </p>
66     */
67    public static final String META_INF_HOME = "META-INF/smartics-properties";
68  
69    /**
70     * The path to the folder within <code>{@value #META_INF_HOME}</code> where
71     * boot properties resources are located.
72     * <p>
73     * The value of this constant is {@value}.
74     * </p>
75     */
76    public static final String BOOT_PROPERTIES_HOME = META_INF_HOME + "/boot";
77  
78    /**
79     * The name of the properties configuration file that provides information for
80     * declarations. This file provides information in archives that provide
81     * property meta data (descriptors).
82     * <p>
83     * The value of this constant is {@value}.
84     * </p>
85     */
86    public static final String DECLARATION_FILE_NAME = "declaration.xml";
87  
88    /**
89     * The name of the properties configuration file that provides information for
90     * definitions. This file provides information in archives that provide
91     * property values.
92     * <p>
93     * The value of this constant is {@value}.
94     * </p>
95     */
96    public static final String DEFINITION_FILE_NAME = "definition.xml";
97  
98    /**
99     * The default location of the properties declaration configuration file.
100    * <p>
101    * The value of this constant is {@value}.
102    * </p>
103    */
104   public static final String DECLARATION_FILE = META_INF_HOME + '/'
105                                                 + DECLARATION_FILE_NAME;
106 
107   /**
108    * The default location of the properties definition configuration file.
109    * <p>
110    * The value of this constant is {@value}.
111    * </p>
112    */
113   public static final String DEFINITION_FILE = META_INF_HOME + '/'
114                                                + DEFINITION_FILE_NAME;
115 
116   /**
117    * The location of the property reports within the <code>META-INF</code>
118    * folder.
119    * <p>
120    * The value of this constant is {@value}.
121    * </p>
122    */
123   private static final String META_INF_PROPERTY_REPORT = META_INF_HOME
124                                                          + "/property-report/";
125 
126   /**
127    * The location of the property set reports within the <code>META-INF</code>
128    * folder.
129    * <p>
130    * The value of this constant is {@value}.
131    * </p>
132    */
133   public static final String META_INF_PROPERTY_SET_REPORT =
134       META_INF_HOME + "/property-set-report/";
135 
136   /**
137    * Specifies the default report location on the home page.
138    * <p>
139    * The value of this constant is {@value}.
140    * </p>
141    */
142   private static final String DEFAULT_REPORT_LOCATION = "/property";
143 
144   // --- members --------------------------------------------------------------
145 
146   /**
147    * The list of supported locales. The list contains locales the context
148    * provides localized information for.
149    *
150    * @serial
151    */
152   private final List<Locale> locales;
153 
154   /**
155    * The URL to the home page of the project. May be <code>null</code>.
156    *
157    * @serial
158    */
159   private final String homePageUrl;
160 
161   /**
162    * The URL to the root directory of smartics properties reports. May be
163    * <code>null</code>.
164    *
165    * @serial
166    */
167   private final String propertiesReportUrl;
168 
169   /**
170    * The mapping of alias names of property reports to their physical names.
171    *
172    * @impl Although {@link PropertyAliasMapping} is not thread-safe, this
173    *       instance provides no write-access to it.
174    * @serial
175    */
176   private final PropertyAliasMapping aliasMapping;
177 
178   // ****************************** Initializer *******************************
179 
180   // ****************************** Constructors ******************************
181 
182   /**
183    * Default constructor.
184    */
185   private PropertiesContext(final Builder builder)
186   {
187     this.locales = builder.locales;
188     this.homePageUrl = builder.homePageUrl;
189     this.propertiesReportUrl = builder.propertiesReportUrl;
190     this.aliasMapping = builder.aliasMapping;
191   }
192 
193   // ****************************** Inner Classes *****************************
194 
195   /**
196    * The builder of {@link PropertiesContext}.
197    */
198   public static final class Builder
199   {
200     // ******************************** Fields ********************************
201 
202     // --- constants ----------------------------------------------------------
203 
204     // --- members ------------------------------------------------------------
205 
206     /**
207      * The list of supported locales. The list contains locales the context
208      * provides localized information for.
209      */
210     private List<Locale> locales = new ArrayList<Locale>();
211 
212     /**
213      * The URL to the home page of the project.
214      */
215     private String homePageUrl;
216 
217     /**
218      * The URL to the root directory of smartics properties reports.
219      */
220     private String propertiesReportUrl;
221 
222     /**
223      * The mapping of alias names of property reports to their physical names.
224      */
225     private final PropertyAliasMapping aliasMapping =
226         new PropertyAliasMapping();
227 
228     // ***************************** Initializer ******************************
229 
230     // ***************************** Constructors *****************************
231 
232     // ***************************** Inner Classes ****************************
233 
234     // ******************************** Methods *******************************
235 
236     // --- init ---------------------------------------------------------------
237 
238     // --- get&set ------------------------------------------------------------
239 
240     /**
241      * Sets the list of supported locales. The list contains locales the context
242      * provides localized information for.
243      *
244      * @param locales the list of supported locales.
245      * @return a reference to the builder.
246      * @throws NullPointerException if {@code locales} is <code>null</code>.
247      */
248     public Builder withLocales(final List<Locale> locales)
249       throws NullPointerException
250     {
251       this.locales = Arg.checkNotNull("locales", locales);
252       return this;
253     }
254 
255     /**
256      * Sets the URL to the home page of the project.
257      *
258      * @param homePageUrl the URL to the home page of the project.
259      * @return a reference to the builder.
260      * @throws NullPointerException if {@code homePageUrl} is <code>null</code>.
261      * @throws IllegalArgumentException if {@code homePageUrl} is blank.
262      */
263     public Builder withHomePageUrl(final String homePageUrl)
264       throws NullPointerException, IllegalArgumentException
265     {
266       this.homePageUrl =
267           normalizeUrl(Arg.checkNotBlank("homePageUrl", homePageUrl));
268       return this;
269     }
270 
271     /**
272      * Sets the URL to the root directory of smartics properties reports.
273      *
274      * @param propertiesReportUrl the URL to the root directory of smartics
275      *          properties reports.
276      * @return a reference to the builder.
277      * @throws NullPointerException if {@code propertiesReportUrl} is
278      *           <code>null</code>.
279      * @throws IllegalArgumentException if {@code propertiesReportUrl} is blank.
280      */
281     public Builder withPropertiesReportUrl(final String propertiesReportUrl)
282       throws NullPointerException, IllegalArgumentException
283     {
284       Arg.checkNotBlank("propertiesReportUrl", propertiesReportUrl);
285       this.propertiesReportUrl = normalizeUrl(propertiesReportUrl);
286       return this;
287     }
288 
289     /**
290      * Adds a new alias to the given physical resource.
291      *
292      * @param alias the new alias.
293      * @param physical the resource the alias refers to.
294      * @return a reference to the builder.
295      * @throws BlankArgumentException if either {@code alias} or
296      *           {@code physical} is blank.
297      * @throws DuplicateAliasException if there is already an alias registered
298      *           that points to a different physical resource.
299      */
300     public Builder withAlias(final String alias, final String physical)
301       throws BlankArgumentException, DuplicateAliasException
302     {
303       aliasMapping.add(alias, physical);
304       return this;
305     }
306 
307     // --- business -----------------------------------------------------------
308 
309     /**
310      * Creates the instance.
311      *
312      * @return the new instance.
313      */
314     public PropertiesContext build()
315     {
316       return new PropertiesContext(this);
317     }
318 
319     // --- object basics ------------------------------------------------------
320   }
321 
322   // ********************************* Methods ********************************
323 
324   // --- init -----------------------------------------------------------------
325 
326   // --- factory --------------------------------------------------------------
327 
328   /**
329    * Creates an empty context.
330    *
331    * @return an empty context.
332    */
333   public static PropertiesContext createEmptyContext()
334   {
335     return new Builder().build();
336   }
337 
338   // --- get&set --------------------------------------------------------------
339 
340   /**
341    * Returns the URL to the home page of the project.
342    *
343    * @return the URL to the home page of the project. May be <code>null</code>.
344    */
345   @CheckForNull
346   public String getHomePageUrl()
347   {
348     return homePageUrl;
349   }
350 
351   /**
352    * Returns the URL to the root directory of smartics properties reports.
353    *
354    * @return the URL to the root directory of smartics properties reports. May
355    *         be <code>null</code>.
356    */
357   @CheckForNull
358   public String getPropertiesReportUrl()
359   {
360     if (propertiesReportUrl == null && homePageUrl != null)
361     {
362       return homePageUrl + DEFAULT_REPORT_LOCATION;
363     }
364     return propertiesReportUrl;
365   }
366 
367   /**
368    * Returns the URL to the index document of smartics properties reports.
369    *
370    * @return the URL to the index document of smartics properties reports. May
371    *         be <code>null</code>.
372    */
373   @CheckForNull
374   public String getPropertiesReportIndexUrl()
375   {
376     return createReportUrl("smartics-properties-report");
377   }
378 
379   /**
380    * Returns the list of supported locales. The list contains locales the
381    * context provides localized information for.
382    *
383    * @return the list of supported locales.
384    */
385   public List<Locale> getLocales()
386   {
387     return locales;
388   }
389 
390   // --- business -------------------------------------------------------------
391 
392   private static String normalizeUrl(final String url)
393   {
394     return StringUtils.chomp(url, "/");
395   }
396 
397   /**
398    * Returns the URL to the relative target.
399    *
400    * @param target the relative URL.
401    * @return the absolute URL to the report or <code>null</code> if no root URL
402    *         is provided by the context.
403    * @throws IllegalArgumentException of {@code target} is blank.
404    */
405   public String createReportUrl(final String target)
406     throws IllegalArgumentException
407   {
408     Arg.checkNotBlank("target", target);
409 
410     if (StringUtils.isEmpty(propertiesReportUrl))
411     {
412       return null;
413     }
414 
415     final String htmlFile = target + ".html";
416     if (target.charAt(0) == '/')
417     {
418       return propertiesReportUrl + htmlFile;
419     }
420     else
421     {
422       return propertiesReportUrl + '/' + htmlFile;
423     }
424   }
425 
426   /**
427    * Returns the URL to the report documentation for the given descriptor.
428    *
429    * @param descriptor the properties descriptor whose report documentation URL
430    *          is requested.
431    * @return the absolute URL to the report.
432    */
433   public String createReportUrl(final PropertyDescriptor descriptor)
434   {
435     final String target = resolve(descriptor);
436     final String url = createReportUrl(target);
437     return url;
438   }
439 
440   private static String resolve(final PropertyDescriptor descriptor)
441   {
442     final DocumentName name = descriptor.getDocumentName();
443     final String target = name.getName();
444     // final String target = descriptor.getKey().toString();
445     return target;
446   }
447 
448   /**
449    * Returns the URL to the XML report in the META-INF directory of the given
450    * descriptor.
451    *
452    * @param descriptor the properties descriptor whose XML report URL is
453    *          requested.
454    * @return the class loader root rooted path.
455    */
456   public String createMetaInfPath(final PropertyDescriptor descriptor)
457   {
458     return createMetaInfPath(descriptor, null);
459   }
460 
461   /**
462    * Returns the URL to the XML report in the META-INF directory of the given
463    * descriptor.
464    *
465    * @param descriptor the properties descriptor whose XML report URL is
466    *          requested.
467    * @param locale the locale to determine the comments.
468    * @return the class loader root rooted path.
469    */
470   @SuppressWarnings("unchecked")
471   public String createMetaInfPath(final PropertyDescriptor descriptor,
472       final Locale locale)
473   {
474     final String target = resolve(descriptor);
475 
476     if (locale != null)
477     {
478       final List<Locale> locales = LocaleUtils.localeLookupList(locale);
479       for (final Locale currentLocale : locales)
480       {
481         final String path =
482             META_INF_PROPERTY_REPORT + target + '_' + currentLocale + ".xml";
483         final ClassLoader loader = descriptor.getClass().getClassLoader(); // NOPMD
484         final URL resource = loader.getResource(path);
485         if (resource != null)
486         {
487           return path;
488         }
489       }
490     }
491 
492     final String path = META_INF_PROPERTY_REPORT + target + ".xml";
493     return path;
494   }
495 
496   /**
497    * Returns the URL to the property set XML report in the META-INF directory of
498    * the given descriptor.
499    *
500    * @param descriptor the properties descriptor whose property set XML report
501    *          URL is requested.
502    * @return the class loader root rooted path.
503    */
504   public String createMetaInfPathPropertySet(final PropertyDescriptor descriptor)
505   {
506     return createMetaInfPathPropertySet(descriptor, null);
507   }
508 
509   /**
510    * Returns the URL to the property set XML report in the META-INF directory of
511    * the given descriptor.
512    *
513    * @param descriptor the properties descriptor whose property set XML report
514    *          URL is requested.
515    * @param locale the locale to determine the comments.
516    * @return the class loader root rooted path.
517    */
518   @SuppressWarnings("unchecked")
519   public String createMetaInfPathPropertySet(
520       final PropertyDescriptor descriptor, final Locale locale)
521   {
522     final String target = descriptor.getKey().getPropertySet();
523 
524     if (locale != null)
525     {
526       final List<Locale> locales = LocaleUtils.localeLookupList(locale);
527       for (final Locale currentLocale : locales)
528       {
529         final String path =
530             META_INF_PROPERTY_SET_REPORT + target + '_' + currentLocale
531                 + ".xml";
532         final ClassLoader loader = descriptor.getClass().getClassLoader(); // NOPMD
533         final URL resource = loader.getResource(path);
534         if (resource != null)
535         {
536           return path;
537         }
538       }
539     }
540 
541     final String path = META_INF_PROPERTY_SET_REPORT + target + ".xml";
542     return path;
543   }
544 
545   /**
546    * Resolves the alias to the target it points to.
547    *
548    * @param alias the alias whose physical resource is requested.
549    * @return the target the alias points to.
550    * @throws BlankArgumentException if {@code alias} is blank.
551    * @throws UnknownAliasException if the alias is not known.
552    */
553   public String resolve(final String alias) throws BlankArgumentException,
554     UnknownAliasException
555   {
556     return aliasMapping.get(alias);
557   }
558 
559   /**
560    * Traverses the registered aliases.
561    *
562    * @param traverser the traverser to use.
563    * @throws NullPointerException if {@code traverser} is <code>null</code>.
564    */
565   public void traverseAliases(final AliasTraverser traverser)
566     throws NullPointerException
567   {
568     aliasMapping.traverse(traverser);
569   }
570 
571   /**
572    * Checks whether any aliases are registered.
573    *
574    * @return <code>true</code> if at least one alias is registered,
575    *         <code>false</code> otherwise.
576    */
577   public boolean hasAliases()
578   {
579     return !aliasMapping.isEmpty();
580   }
581 
582   // --- object basics --------------------------------------------------------
583 
584   /**
585    * Returns the string representation of the object.
586    *
587    * @return the string representation of the object.
588    */
589   @Override
590   public String toString()
591   {
592     return ToStringBuilder.reflectionToString(this);
593   }
594 }