001// Copyright 2004, 2005 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry.spec; 016 017import java.util.ArrayList; 018import java.util.Collections; 019import java.util.HashMap; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.hivemind.ApplicationRuntimeException; 025import org.apache.hivemind.Location; 026import org.apache.hivemind.Resource; 027import org.apache.hivemind.util.ToStringBuilder; 028import org.apache.tapestry.Tapestry; 029 030/** 031 * Specification for a library. {@link org.apache.tapestry.spec.ApplicationSpecification}is a 032 * specialized kind of library. 033 * 034 * @author Howard Lewis Ship 035 * @since 2.2bv 036 */ 037 038public class LibrarySpecification extends LocatablePropertyHolder implements ILibrarySpecification 039{ 040 /** 041 * Map of page name to page specification path. 042 */ 043 044 private Map _pages; 045 046 /** 047 * Map of component alias to component specification path. 048 */ 049 private Map _components; 050 051 /** 052 * Map of library id to library specification path. 053 */ 054 055 private Map _libraries; 056 057 private String _description; 058 059 /** 060 * Map of extension name to {@link IExtensionSpecification}. 061 */ 062 063 private Map _extensions; 064 065 /** 066 * Map of extension name to Object for instantiated extensions. 067 */ 068 069 private Map _instantiatedExtensions; 070 071 /** 072 * The XML Public Id used when the library specification was read (if applicable). 073 * 074 * @since 2.2 075 */ 076 077 private String _publicId; 078 079 /** 080 * The location of the specification. 081 */ 082 083 private Resource _specificationLocation; 084 085 public String getLibrarySpecificationPath(String id) 086 { 087 return (String) get(_libraries, id); 088 } 089 090 /** 091 * Sets the specification path for an embedded library. 092 * 093 * @throws IllegalArgumentException 094 * if a library with the given id already exists 095 */ 096 097 public void setLibrarySpecificationPath(String id, String path) 098 { 099 if (_libraries == null) 100 _libraries = new HashMap(); 101 102 if (_libraries.containsKey(id)) 103 throw new IllegalArgumentException(Tapestry.format( 104 "LibrarySpecification.duplicate-child-namespace-id", 105 id)); 106 107 _libraries.put(id, path); 108 } 109 110 public List getLibraryIds() 111 { 112 return sortedKeys(_libraries); 113 } 114 115 public String getPageSpecificationPath(String name) 116 { 117 return (String) get(_pages, name); 118 } 119 120 public void setPageSpecificationPath(String name, String path) 121 { 122 if (_pages == null) 123 _pages = new HashMap(); 124 125 if (_pages.containsKey(name)) 126 throw new IllegalArgumentException(Tapestry.format( 127 "LibrarySpecification.duplicate-page-name", 128 name)); 129 130 _pages.put(name, path); 131 } 132 133 public List getPageNames() 134 { 135 return sortedKeys(_pages); 136 } 137 138 public void setComponentSpecificationPath(String alias, String path) 139 { 140 if (_components == null) 141 _components = new HashMap(); 142 143 if (_components.containsKey(alias)) 144 throw new IllegalArgumentException(Tapestry.format( 145 "LibrarySpecification.duplicate-component-alias", 146 alias)); 147 148 _components.put(alias, path); 149 } 150 151 public String getComponentSpecificationPath(String alias) 152 { 153 return (String) get(_components, alias); 154 } 155 156 /** 157 * @since 3.0 158 */ 159 160 public List getComponentTypes() 161 { 162 return sortedKeys(_components); 163 } 164 165 public String getServiceClassName(String name) 166 { 167 throw new UnsupportedOperationException(); 168 } 169 170 public List getServiceNames() 171 { 172 return Collections.EMPTY_LIST; 173 } 174 175 public void setServiceClassName(String name, String className) 176 { 177 throw new UnsupportedOperationException(); 178 } 179 180 private List sortedKeys(Map map) 181 { 182 if (map == null) 183 return Collections.EMPTY_LIST; 184 185 List result = new ArrayList(map.keySet()); 186 187 Collections.sort(result); 188 189 return result; 190 } 191 192 private Object get(Map map, Object key) 193 { 194 if (map == null) 195 return null; 196 197 return map.get(key); 198 } 199 200 /** 201 * Returns the documentation for this library.. 202 */ 203 204 public String getDescription() 205 { 206 return _description; 207 } 208 209 /** 210 * Sets the documentation for this library. 211 */ 212 213 public void setDescription(String description) 214 { 215 _description = description; 216 } 217 218 /** 219 * Returns a Map of extensions; key is extension name, value is 220 * {@link org.apache.tapestry.spec.IExtensionSpecification}. May return null. The returned Map 221 * is immutable. 222 */ 223 224 public Map getExtensionSpecifications() 225 { 226 if (_extensions == null) 227 return null; 228 229 return Collections.unmodifiableMap(_extensions); 230 } 231 232 /** 233 * Adds another extension specification. 234 * 235 * @throws IllegalArgumentException 236 * if an extension with the given name already exists. 237 */ 238 239 public void addExtensionSpecification(String name, IExtensionSpecification extension) 240 { 241 if (_extensions == null) 242 _extensions = new HashMap(); 243 244 if (_extensions.containsKey(name)) 245 throw new IllegalArgumentException(Tapestry.format( 246 "LibrarySpecification.duplicate-extension-name", 247 this, 248 name)); 249 250 _extensions.put(name, extension); 251 } 252 253 /** 254 * Returns a sorted List of the names of all extensions. May return the empty list, but won't 255 * return null. 256 */ 257 258 public synchronized List getExtensionNames() 259 { 260 return sortedKeys(_instantiatedExtensions); 261 } 262 263 /** 264 * Returns the named IExtensionSpecification, or null if it doesn't exist. 265 */ 266 267 public IExtensionSpecification getExtensionSpecification(String name) 268 { 269 if (_extensions == null) 270 return null; 271 272 return (IExtensionSpecification) _extensions.get(name); 273 } 274 275 /** 276 * Returns true if this library specification has a specification for the named extension. 277 */ 278 279 public boolean checkExtension(String name) 280 { 281 if (_extensions == null) 282 return false; 283 284 return _extensions.containsKey(name); 285 } 286 287 /** 288 * Returns an instantiated extension. Extensions are created as needed and cached for later use. 289 * 290 * @throws IllegalArgumentException 291 * if no extension specification exists for the given name. 292 */ 293 294 public synchronized Object getExtension(String name) 295 { 296 return getExtension(name, null); 297 } 298 299 /** @since 3.0 * */ 300 301 public synchronized Object getExtension(String name, Class typeConstraint) 302 { 303 if (_instantiatedExtensions == null) 304 _instantiatedExtensions = new HashMap(); 305 306 Object result = _instantiatedExtensions.get(name); 307 IExtensionSpecification spec = getExtensionSpecification(name); 308 309 if (spec == null) 310 throw new IllegalArgumentException(Tapestry.format( 311 "LibrarySpecification.no-such-extension", 312 name)); 313 314 if (result == null) 315 { 316 317 result = spec.instantiateExtension(); 318 319 _instantiatedExtensions.put(name, result); 320 } 321 322 if (typeConstraint != null) 323 applyTypeConstraint(name, result, typeConstraint, spec.getLocation()); 324 325 return result; 326 } 327 328 /** 329 * Checks that an extension conforms to the supplied type constraint. 330 * 331 * @throws IllegalArgumentException 332 * if the extension fails the check. 333 * @since 3.0 334 */ 335 336 protected void applyTypeConstraint(String name, Object extension, Class typeConstraint, 337 Location location) 338 { 339 Class extensionClass = extension.getClass(); 340 341 // Can you assign an instance of the extension to a variable 342 // of type typeContraint legally? 343 344 if (typeConstraint.isAssignableFrom(extensionClass)) 345 return; 346 347 String key = typeConstraint.isInterface() ? "LibrarySpecification.extension-does-not-implement-interface" 348 : "LibrarySpecification.extension-not-a-subclass"; 349 350 throw new ApplicationRuntimeException(Tapestry.format( 351 key, 352 name, 353 extensionClass.getName(), 354 typeConstraint.getName()), location, null); 355 } 356 357 /** 358 * Invoked after the entire specification has been constructed to instantiate any extensions 359 * marked immediate. 360 */ 361 362 public synchronized void instantiateImmediateExtensions() 363 { 364 if (_extensions == null) 365 return; 366 367 Iterator i = _extensions.entrySet().iterator(); 368 369 while (i.hasNext()) 370 { 371 Map.Entry entry = (Map.Entry) i.next(); 372 373 IExtensionSpecification spec = (IExtensionSpecification) entry.getValue(); 374 375 if (!spec.isImmediate()) 376 continue; 377 378 String name = (String) entry.getKey(); 379 380 getExtension(name); 381 } 382 383 } 384 385 /** 386 * Returns the extensions map. 387 * 388 * @return Map of objects. 389 */ 390 391 protected Map getExtensions() 392 { 393 return _extensions; 394 } 395 396 /** 397 * Updates the extension map. 398 * 399 * @param extension 400 * A Map of extension specification paths keyed on extension id. 401 * <p> 402 * The map is retained, not copied. 403 */ 404 405 protected void setExtensions(Map extension) 406 { 407 _extensions = extension; 408 } 409 410 /** 411 * Returns the libraries map. 412 * 413 * @return Map of {@link LibrarySpecification}. 414 */ 415 416 protected Map getLibraries() 417 { 418 return _libraries; 419 } 420 421 /** 422 * Updates the library map. 423 * 424 * @param libraries 425 * A Map of library specification paths keyed on library id. 426 * <p> 427 * The map is retained, not copied. 428 */ 429 430 protected void setLibraries(Map libraries) 431 { 432 _libraries = libraries; 433 } 434 435 /** 436 * Returns the pages map. 437 * 438 * @return Map of {@link IComponentSpecification}. 439 */ 440 441 protected Map getPages() 442 { 443 return _pages; 444 } 445 446 /** 447 * Updates the page map. 448 * 449 * @param pages 450 * A Map of page specification paths keyed on page id. 451 * <p> 452 * The map is retained, not copied. 453 */ 454 455 protected void setPages(Map pages) 456 { 457 _pages = pages; 458 } 459 460 /** 461 * Returns the services. 462 * 463 * @return Map of service class names. 464 * @deprecated To be removed in release 4.1. 465 */ 466 467 protected Map getServices() 468 { 469 return Collections.EMPTY_MAP; 470 } 471 472 /** 473 * Updates the services map. 474 * 475 * @param services 476 * A Map of the fully qualified names of classes which implement 477 * {@link org.apache.tapestry.engine.IEngineService}keyed on service id. 478 * <p> 479 * The map is retained, not copied. 480 * @deprecated To be removed in release 4.1. 481 */ 482 483 protected void setServices(Map services) 484 { 485 } 486 487 /** 488 * Returns the components map. 489 * 490 * @return Map of {@link IContainedComponent}. 491 */ 492 493 protected Map getComponents() 494 { 495 return _components; 496 } 497 498 /** 499 * Updates the components map. 500 * 501 * @param components 502 * A Map of {@link IContainedComponent}keyed on component id. The map is retained, 503 * not copied. 504 */ 505 506 protected void setComponents(Map components) 507 { 508 _components = components; 509 } 510 511 /** 512 * Returns the XML Public Id for the library file, or null if not applicable. 513 * <p> 514 * This method exists as a convienience for the Spindle plugin. A previous method used an 515 * arbitrary version string, the public id is more useful and less ambiguous. 516 */ 517 518 public String getPublicId() 519 { 520 return _publicId; 521 } 522 523 public void setPublicId(String publicId) 524 { 525 _publicId = publicId; 526 } 527 528 /** @since 3.0 * */ 529 530 public Resource getSpecificationLocation() 531 { 532 return _specificationLocation; 533 } 534 535 /** @since 3.0 * */ 536 537 public void setSpecificationLocation(Resource specificationLocation) 538 { 539 _specificationLocation = specificationLocation; 540 } 541 542 /** @since 3.0 * */ 543 544 public synchronized String toString() 545 { 546 ToStringBuilder builder = new ToStringBuilder(this); 547 548 builder.append("components", _components); 549 builder.append("description", _description); 550 builder.append("instantiatedExtensions", _instantiatedExtensions); 551 builder.append("libraries", _libraries); 552 builder.append("pages", _pages); 553 builder.append("publicId", _publicId); 554 builder.append("specificationLocation", _specificationLocation); 555 556 extendDescription(builder); 557 558 return builder.toString(); 559 } 560 561 /** 562 * Does nothing, subclasses may override to add additional description. 563 * 564 * @see #toString() 565 * @since 3.0 566 */ 567 568 protected void extendDescription(ToStringBuilder builder) 569 { 570 } 571 572}