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 }