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 }