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
015 package org.apache.tapestry.spec;
016
017 import java.util.ArrayList;
018 import java.util.Collections;
019 import java.util.HashMap;
020 import java.util.Iterator;
021 import java.util.List;
022 import java.util.Map;
023
024 import org.apache.hivemind.ApplicationRuntimeException;
025 import org.apache.hivemind.Location;
026 import org.apache.hivemind.Resource;
027 import org.apache.hivemind.util.ToStringBuilder;
028 import 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
038 public 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 }