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.tutorial.property;
17  
18  import static org.hamcrest.MatcherAssert.assertThat;
19  import static org.hamcrest.Matchers.containsString;
20  import static org.hamcrest.Matchers.empty;
21  import static org.hamcrest.Matchers.equalTo;
22  import static org.hamcrest.Matchers.is;
23  import static org.hamcrest.Matchers.not;
24  import static org.hamcrest.Matchers.nullValue;
25  
26  import java.net.MalformedURLException;
27  import java.net.URL;
28  
29  import org.junit.Before;
30  import org.junit.Test;
31  
32  import de.smartics.projectdoc.annotations.DocCategory;
33  import de.smartics.projectdoc.annotations.Document;
34  import de.smartics.projectdoc.annotations.topic.DocChapter;
35  import de.smartics.projectdoc.annotations.topic.DocSection;
36  import de.smartics.properties.api.config.domain.ConfigurationNotFoundException;
37  import de.smartics.properties.api.core.annotations.AccessType;
38  import de.smartics.properties.api.core.annotations.PropertyDefinitionTime;
39  import de.smartics.properties.api.core.annotations.PropertyLifecycle.UpdateInterval;
40  import de.smartics.properties.api.core.domain.DocumentMetaData;
41  import de.smartics.properties.api.core.domain.PropertyDescriptor;
42  import de.smartics.properties.api.core.domain.PropertyExpression;
43  import de.smartics.properties.api.core.domain.PropertyKey;
44  import de.smartics.properties.api.core.domain.PropertyType;
45  import de.smartics.properties.impl.config.classpath.ClasspathConfigurationProperties;
46  import de.smartics.properties.impl.config.classpath.ClasspathConfigurationPropertiesFactory;
47  
48  /**
49   * This tutorial introduces the elements of a property and gives examples on how
50   * properties are declared.
51   */
52  @Document(title = "What is a Property?", sortKey = "basics0030")
53  @DocCategory({ "basics" })
54  // @DocTopic(path="basics", step="30")
55  public class PropertyTutorial
56  {
57    private ClasspathConfigurationProperties config; // NOPMD
58  
59    private ApplicationProperties properties;
60  
61    @Before
62    public void setUp()
63    {
64      config = createConfiguration();
65      properties = config.getProperties(ApplicationProperties.class);
66    }
67  
68    private static ClasspathConfigurationProperties createConfiguration()
69    {
70      final ClasspathConfigurationPropertiesFactory factory =
71          new ClasspathConfigurationPropertiesFactory();
72      final ClasspathConfigurationProperties config = factory.create();
73      config.addClassPathProperties(ApplicationProperties.class);
74      return config;
75    }
76  
77    /**
78     * <p>
79     * The declaration states the purpose of a property. All meta data about the
80     * property is specified. Everything to be known about the property, besides
81     * its actual value, is fixed with its declaration. The information of the
82     * declaration is stored with a
83     * {@link de.smartics.properties.api.core.domain.PropertyDescriptor property
84     * descriptor}.
85     * </p>
86     * <p>
87     * We saw the declaration of a property in
88     * {@link de.smartics.properties.tutorial.onestringproperty.OneStringPropertyTutorial}
89     * . A simple property of type {@link String} has been declared. We can
90     * request a method to access the property descriptor of that property by
91     * adding a method that returns an instance of
92     * {@link de.smartics.properties.api.core.domain.PropertyDescriptor
93     * PropertyDescriptor}.
94     * </p>
95     * {@insertCode source="ApplicationProperties"}
96     * <p>
97     * Besides the method {@link ApplicationProperties#hostPropertyDescriptor()
98     * hostPropertyDescriptor()} to access the property descriptor, this example
99     * also shows real comments - in form of <a href=
100    * "http://www.oracle.com/technetwork/java/javase/documentation/index-jsp-135444.html"
101    * >Javadoc</a>. Let's have a look how the information stored in a property
102    * descriptor is accessed.
103    * </p>
104    * {@insertCode}
105    * <p>
106    * At {@$1} we access the data type of the property: It is a
107    * {@link String} in this case since the return type is declared as such.
108    * Second, we see that we can access the documentation in form of a comment.
109    * This is the text that is given as Javadoc comment given to the property
110    * method.
111    * </p>
112    * <p>
113    * At {@$2} we have access to the property set name and property key,
114    * which turns out to be <code>tutorial.property</code> (as given with the
115    * <code>@PropertiesSet</code> annotation) and <code>host</code>. This
116    * information can be accessed individually without retrieving the property
117    * descriptor. For more information on this topic please refer to {@link }.
118    * </p>
119    * <p>
120    * At {@$3} we see that there is no expression that constructs a default
121    * value if no explicit value is given. There are no constraints a property
122    * value has to meet. Please refer to the tutorials on
123    * {@link de.smartics.properties.tutorial.property.expressions.PropertyExpressionsTutorial
124    * expressions} and
125    * {@link de.smartics.properties.tutorial.property.constraints.PropertyConstraintsTutorial
126    * constraints} for more information on those topics.
127    * </p>
128    * <p>
129    * At {@$4} we see that properties are per default <i>read-only</i>.
130    * Consequently the update time is set to
131    * {@link UpdateInterval#NO_AUTOMATIC_UPDATE NO_AUTOMATIC_UPDATE}. The time
132    * the property is set is {@link PropertyDefinitionTime#STARTUP at startup}
133    * (please refer to {@link PropertyDefinitionTime} for a list of valid
134    * configuration times. The tutorial on
135    * {@link de.smartics.properties.tutorial.property.deployment.DeploymentTutorial
136    * deployment} provides more information about these properties.
137    * </p>
138    * <p>
139    * At {@$5} there is access to properties meta data. This information
140    * allows to add meta data commonly used with documents, such as tags or
141    * categories. We have a look at all the values in the next section.
142    * </p>
143    */
144   @DocChapter
145   @Test
146   public void declarationOfAProperty()
147   {
148     final PropertyDescriptor descriptor = properties.hostPropertyDescriptor();
149 
150     // {1}
151     final PropertyType type = descriptor.getType();
152     assertThat(type.getType().getName(), is(String.class.getName()));
153     assertThat(
154         descriptor.getComment().getText(),
155         is(equalTo("Returns the name of the host. This name is used to construct URLs by adding"
156                    + " a protocol, an optional port and request path. It is allowed to set an IP"
157                    + " address as specified in <a href=\"http://www.ietf.org/rfc/rfc2396.txt\">RFC"
158                    + " 2396</a>.")));
159 
160     // {2}
161     final PropertyKey key = descriptor.getKey();
162     assertThat(key.getPropertySet(), is("tutorial.property"));
163     assertThat(key.getName(), is("host"));
164 
165     // {3}
166     assertThat(descriptor.getDefaultExpression(),
167         is(PropertyExpression.NO_EXPRESSION));
168     assertThat(descriptor.getConstraints().isEmpty(), is(true));
169 
170     // {4}
171     assertThat(descriptor.getAccessType(), is(AccessType.READ_ONLY));
172     assertThat(descriptor.getUpdateIntervalInMs(),
173         is(UpdateInterval.NO_AUTOMATIC_UPDATE));
174     assertThat(descriptor.getConfigurationTime(),
175         is(PropertyDefinitionTime.STARTUP));
176 
177     // {5}
178     final DocumentMetaData metaData = descriptor.getDocumentMetaData();
179     assertThat(metaData, is(not(nullValue())));
180   }
181 
182   /**
183    * {@insertCode}
184    * <p>
185    * Document meta data allows a documentation on a property to be inserted into
186    * the context of a project documentation. They allow to categorize or tag a
187    * document, insert them in a hierarchy by specifying one or more parent
188    * documents, provide information about the intended audience.
189    * </p>
190    * <p>
191    * The author of a property may also add notes, short and summary
192    * descriptions, a title and a sort key.
193    * </p>
194    * <p>
195    * To uniquely identify a document the author may specify a name that is
196    * unique within a given space (there are no unique constraint on the title).
197    * </p>
198    * <p>
199    * What you have seen in the code snippet is the default if none of this
200    * information has been specified. All information besides title, space and
201    * name are <code>null</code> or empty. For an example to add such
202    * information, please refer to
203    * {@link de.smartics.properties.tutorial.property.metadata.PropertyMetaDataTutorial}
204    * for detailed information.
205    * </p>
206    */
207   @DocSection
208   @Test
209   public void accessingPropertyDocumentMetaData()
210   {
211     final PropertyDescriptor descriptor = properties.hostPropertyDescriptor();
212 
213     final DocumentMetaData metaData = descriptor.getDocumentMetaData();
214     assertThat(metaData.getCategories(), is(empty()));
215     assertThat(metaData.getTags(), is(empty()));
216     assertThat(metaData.getParents(), is(empty()));
217     assertThat(metaData.getAudience(), is(empty()));
218 
219     assertThat(metaData.getNotes(), is(empty()));
220     assertThat(metaData.getShortDescription(), is(nullValue()));
221     assertThat(metaData.getSummary(), is(nullValue()));
222     assertThat(metaData.getTitle(), is("tutorial.property.host"));
223     assertThat(metaData.getSortKey(), is(nullValue()));
224 
225     assertThat(
226         metaData.getSpace(),
227         is("property:de.smartics.properties.tutorial.property.ApplicationProperties"));
228     assertThat(metaData.getName(), is("tutorial.property.host"));
229   }
230 
231   /**
232    * <p>
233    * There is nothing new here according to the configuration shown in
234    * {@link de.smartics.properties.tutorial.onestringproperty.OneStringPropertyTutorial}
235    * . We still use a configuration that selects property definitions in form of
236    * properties files on the classpath. Again: The location of the properties
237    * file is the same as the location of the interface that declares the
238    * property set.
239    * </p>
240    * {@insertCode file="ApplicationProperties.properties"}
241    * <p>
242    * In this example you see the definition of one {@link String} and one
243    * {@link URL} property.
244    * </p>
245    */
246   @DocChapter
247   public void definitionOfAPropertyValue()
248   {
249   }
250 
251   /**
252    * There is also nothing special about accessing the property and it works
253    * exactly as shown in
254    * {@link de.smartics.properties.tutorial.onestringproperty.OneStringPropertyTutorial}
255    * . Here we show that some property types (e.g. {@link URL}) are
256    * automatically converted.
257    */
258   @DocChapter
259   @Test
260   public void accessingAPropertyValue() throws MalformedURLException
261   {
262     final String host = properties.host();
263     assertThat(host, is("localhost"));
264     final URL url = properties.serverUrl();
265     assertThat(url, is(new URL("http://example.com/index.html")));
266   }
267 
268   /**
269    * You may encounter on of the following problems.
270    */
271   @DocChapter
272   public void troubleShooting()
273   {
274   }
275 
276   /**
277    * <p>
278    * If the properties file to define the properties (associate the declarations
279    * with property values) cannot be found, the following exception is thrown:
280    * </p>
281    *
282    * <pre>
283    * <![CDATA[de.smartics.properties.api.config.domain.ConfigurationNotFoundException: Configuration '<no named key>' cannot be loaded from: ApplicationProperties.properties
284    *   at de.smartics.properties.spi.config.support.AbstractInMemoryConfigurationProperties.loadDefault(AbstractInMemoryConfigurationProperties.java:166)
285    *   at de.smartics.properties.spi.config.support.AbstractInMemoryConfigurationProperties.addClassPathProperties(AbstractInMemoryConfigurationProperties.java:148)
286    *   at de.smartics.properties.tutorial.property.metadata.PropertyMetaDataTutorial.createConfiguration(PropertyMetaDataTutorial.java:60)
287    *   at de.smartics.properties.tutorial.property.metadata.PropertyMetaDataTutorial.setUp(PropertyMetaDataTutorial.java:51)
288    *   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
289    *   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
290    *   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
291    *   at java.lang.reflect.Method.invoke(Method.java:597)
292    *   at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
293    *   at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
294    *   at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
295    *   at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)
296    *   at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
297    *   at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
298    *   at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
299    *   at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
300    *   at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
301    *   at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
302    *   at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
303    *   at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
304    *   at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
305    *   at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
306    *   at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
307    *   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
308    *   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
309    *   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
310    *   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)]]>
311    * </pre>
312    * <p>
313    * If a configuration is not found, the source for property values are not
314    * found. In our case we read the property values from the class path in form
315    * of a properties file. It depends on the configuration implementation where
316    * the property values have to be placed. In our case the requested location
317    * of <code>ApplicationProperties.properties</code> is the same classpath
318    * location as the <code>ApplicationProperties</code> interface.
319    * </p>
320    * {@insertCode}
321    */
322   @DocSection
323   @Test(expected = ConfigurationNotFoundException.class)
324   public void noPropertiesFile()
325   {
326     try
327     {
328       config.addClassPathProperties(MissingProperties.class);
329     }
330     catch (final ConfigurationNotFoundException e)
331     {
332       assertThat(
333           e.getMessage(),
334           containsString("Configuration ':/::' cannot be loaded from 'MissingProperties.properties'"));
335       throw e;
336     }
337   }
338 
339   /**
340    * <p>
341    * If the name of the property set is not mentioned as a prefix to the name of
342    * the property, the property cannot be found.
343    * </p>
344    * {@insertCode file="MissingPropertySetNameProperties.properties"}
345    * <p>
346    * In this case the property is not mandatory and therefore the value for this
347    * property is simply <code>null</code>.
348    * </p>
349    * {@insertCode}
350    */
351   @DocSection
352   @Test
353   public void omittingTheNameOfThePropertySet()
354   {
355     config.addClassPathProperties(MissingPropertySetNameProperties.class);
356     final MissingPropertySetNameProperties properties =
357         config.getProperties(MissingPropertySetNameProperties.class);
358     final String value = properties.missingPropertySetName();
359     assertThat(value, is(nullValue()));
360   }
361 }