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    }