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.parse;
016    
017    import java.io.BufferedInputStream;
018    import java.io.IOException;
019    import java.io.InputStream;
020    import java.net.URL;
021    import java.util.HashMap;
022    import java.util.Iterator;
023    import java.util.Map;
024    
025    import javax.xml.parsers.SAXParser;
026    import javax.xml.parsers.SAXParserFactory;
027    
028    import org.apache.commons.logging.Log;
029    import org.apache.commons.logging.LogFactory;
030    import org.apache.hivemind.ClassResolver;
031    import org.apache.hivemind.ErrorHandler;
032    import org.apache.hivemind.HiveMind;
033    import org.apache.hivemind.Location;
034    import org.apache.hivemind.Resource;
035    import org.apache.hivemind.impl.DefaultErrorHandler;
036    import org.apache.hivemind.impl.LocationImpl;
037    import org.apache.hivemind.parse.AbstractParser;
038    import org.apache.tapestry.INamespace;
039    import org.apache.tapestry.Tapestry;
040    import org.apache.tapestry.bean.BindingBeanInitializer;
041    import org.apache.tapestry.bean.LightweightBeanInitializer;
042    import org.apache.tapestry.binding.BindingConstants;
043    import org.apache.tapestry.binding.BindingSource;
044    import org.apache.tapestry.coerce.ValueConverter;
045    import org.apache.tapestry.spec.BeanLifecycle;
046    import org.apache.tapestry.spec.BindingType;
047    import org.apache.tapestry.spec.IApplicationSpecification;
048    import org.apache.tapestry.spec.IAssetSpecification;
049    import org.apache.tapestry.spec.IBeanSpecification;
050    import org.apache.tapestry.spec.IBindingSpecification;
051    import org.apache.tapestry.spec.IComponentSpecification;
052    import org.apache.tapestry.spec.IContainedComponent;
053    import org.apache.tapestry.spec.IExtensionSpecification;
054    import org.apache.tapestry.spec.ILibrarySpecification;
055    import org.apache.tapestry.spec.IListenerBindingSpecification;
056    import org.apache.tapestry.spec.IParameterSpecification;
057    import org.apache.tapestry.spec.IPropertySpecification;
058    import org.apache.tapestry.spec.InjectSpecification;
059    import org.apache.tapestry.spec.SpecFactory;
060    import org.apache.tapestry.util.IPropertyHolder;
061    import org.apache.tapestry.util.RegexpMatcher;
062    import org.apache.tapestry.util.xml.DocumentParseException;
063    import org.apache.tapestry.util.xml.InvalidStringException;
064    import org.xml.sax.InputSource;
065    import org.xml.sax.SAXException;
066    import org.xml.sax.SAXParseException;
067    
068    /**
069     * Parses the different types of Tapestry specifications.
070     * <p>
071     * Not threadsafe; it is the callers responsibility to ensure thread safety.
072     * 
073     * @author Howard Lewis Ship
074     */
075    public class SpecificationParser extends AbstractParser implements ISpecificationParser
076    {
077        private static final String IDENTIFIER_PATTERN = "_?[a-zA-Z]\\w*";
078    
079        private static final String EXTENDED_IDENTIFIER_PATTERN = "_?[a-zA-Z](\\w|-)*";
080    
081        /**
082         * Perl5 pattern for asset names. Letter, followed by letter, number or underscore. Also allows
083         * the special "$template" value.
084         * 
085         * @since 2.2
086         */
087    
088        public static final String ASSET_NAME_PATTERN = "(\\$template)|("
089                + Tapestry.SIMPLE_PROPERTY_NAME_PATTERN + ")";
090    
091        /**
092         * Perl5 pattern for helper bean names. Letter, followed by letter, number or underscore.
093         * 
094         * @since 2.2
095         */
096    
097        public static final String BEAN_NAME_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;
098    
099        /**
100         * Perl5 pattern for component type (which was known as an "alias" in earlier versions of
101         * Tapestry). This is either a simple property name, or a series of property names seperated by
102         * slashes (the latter being new in Tapestry 4.0). This defines a literal that can appear in a
103         * library or application specification.
104         * 
105         * @since 2.2
106         */
107    
108        public static final String COMPONENT_ALIAS_PATTERN = "^(" + IDENTIFIER_PATTERN + "/)*"
109                + IDENTIFIER_PATTERN + "$";
110    
111        /**
112         * Perl5 pattern for component ids. Letter, followed by letter, number or underscore.
113         * 
114         * @since 2.2
115         */
116    
117        public static final String COMPONENT_ID_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;
118    
119        /**
120         * Perl5 pattern for component types (i.e., the type attribute of the &lt;component&gt;
121         * element). Component types are an optional namespace prefix followed by a component type
122         * (within the library defined by the namespace). Starting in 4.0, the type portion is actually
123         * a series of identifiers seperated by slashes.
124         * 
125         * @since 2.2
126         */
127    
128        public static final String COMPONENT_TYPE_PATTERN = "^(" + IDENTIFIER_PATTERN + ":)?" + "("
129                + IDENTIFIER_PATTERN + "/)*" + IDENTIFIER_PATTERN + "$";
130    
131        /**
132         * We can share a single map for all the XML attribute to object conversions, since the keys are
133         * unique.
134         */
135    
136        private final Map CONVERSION_MAP = new HashMap();
137    
138        /**
139         * Extended version of {@link Tapestry.SIMPLE_PROPERTY_NAME_PATTERN}, but allows a series of
140         * individual property names, seperated by periods. In addition, each name within the dotted
141         * sequence is allowed to contain dashes.
142         * 
143         * @since 2.2
144         */
145    
146        public static final String EXTENDED_PROPERTY_NAME_PATTERN = "^" + EXTENDED_IDENTIFIER_PATTERN
147                + "(\\." + EXTENDED_IDENTIFIER_PATTERN + ")*$";
148    
149        /**
150         * Per5 pattern for extension names. Letter followed by letter, number, dash, period or
151         * underscore.
152         * 
153         * @since 2.2
154         */
155    
156        public static final String EXTENSION_NAME_PATTERN = EXTENDED_PROPERTY_NAME_PATTERN;
157    
158        /**
159         * Perl5 pattern for library ids. Letter followed by letter, number or underscore.
160         * 
161         * @since 2.2
162         */
163    
164        public static final String LIBRARY_ID_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;
165    
166        /** @since 4.0 */
167        private final Log _log;
168    
169        /** @since 4.0 */
170        private final ErrorHandler _errorHandler;
171    
172        /**
173         * Set to true if parsing the 4.0 DTD.
174         * 
175         * @since 4.0
176         */
177    
178        private boolean _DTD_4_0;
179    
180        /**
181         * Perl5 pattern for page names. Page names appear in library and application specifications, in
182         * the &lt;page&gt; element. Starting with 4.0, the page name may look more like a path name,
183         * consisting of a number of ids seperated by slashes. This is used to determine the folder
184         * which contains the page specification or the page's template.
185         * 
186         * @since 2.2
187         */
188    
189        public static final String PAGE_NAME_PATTERN = "^" + IDENTIFIER_PATTERN + "(/"
190                + IDENTIFIER_PATTERN + ")*$";
191    
192        /**
193         * Perl5 pattern that parameter names must conform to. Letter, followed by letter, number or
194         * underscore.
195         * 
196         * @since 2.2
197         */
198    
199        public static final String PARAMETER_NAME_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;
200    
201        /**
202         * Perl5 pattern that property names (that can be connected to parameters) must conform to.
203         * Letter, followed by letter, number or underscore.
204         * 
205         * @since 2.2
206         */
207    
208        public static final String PROPERTY_NAME_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;
209    
210        /**
211         * Perl5 pattern for service names. Letter followed by letter, number, dash, underscore or
212         * period.
213         * 
214         * @since 2.2
215         * @deprecated As of release 4.0, the &lt;service&gt; element (in 3.0 DTDs) is no longer
216         *             supported.
217         */
218    
219        public static final String SERVICE_NAME_PATTERN = EXTENDED_PROPERTY_NAME_PATTERN;
220    
221        private static final int STATE_ALLOW_DESCRIPTION = 2000;
222    
223        private static final int STATE_ALLOW_PROPERTY = 2001;
224    
225        private static final int STATE_APPLICATION_SPECIFICATION_INITIAL = 1002;
226    
227        private static final int STATE_BEAN = 4;
228    
229        /** Very different between 3.0 and 4.0 DTD */
230    
231        private static final int STATE_BINDING_3_0 = 7;
232    
233        /** @since 4.0 */
234    
235        private static final int STATE_BINDING = 100;
236    
237        private static final int STATE_COMPONENT = 6;
238    
239        private static final int STATE_COMPONENT_SPECIFICATION = 1;
240    
241        private static final int STATE_COMPONENT_SPECIFICATION_INITIAL = 1000;
242    
243        private static final int STATE_CONFIGURE = 14;
244    
245        private static final int STATE_DESCRIPTION = 2;
246    
247        private static final int STATE_EXTENSION = 13;
248    
249        private static final int STATE_LIBRARY_SPECIFICATION = 12;
250    
251        private static final int STATE_LIBRARY_SPECIFICATION_INITIAL = 1003;
252    
253        private static final int STATE_LISTENER_BINDING = 8;
254    
255        private static final int STATE_NO_CONTENT = 3000;
256    
257        private static final int STATE_PAGE_SPECIFICATION = 11;
258    
259        private static final int STATE_PAGE_SPECIFICATION_INITIAL = 1001;
260    
261        private static final int STATE_META = 3;
262    
263        private static final int STATE_PROPERTY = 10;
264    
265        private static final int STATE_SET = 5;
266    
267        /** 3.0 DTD only */
268        private static final int STATE_STATIC_BINDING = 9;
269    
270        /** @since 3.0 */
271    
272        public static final String TAPESTRY_DTD_3_0_PUBLIC_ID = "-//Apache Software Foundation//Tapestry Specification 3.0//EN";
273    
274        /** @since 4.0 */
275    
276        public static final String TAPESTRY_DTD_4_0_PUBLIC_ID = "-//Apache Software Foundation//Tapestry Specification 4.0//EN";
277    
278        /**
279         * The attributes of the current element, as a map (string keyed on string).
280         */
281    
282        private Map _attributes;
283    
284        /**
285         * The name of the current element.
286         */
287    
288        private String _elementName;
289    
290        /** @since 1.0.9 */
291    
292        private final SpecFactory _factory;
293    
294        private RegexpMatcher _matcher = new RegexpMatcher();
295    
296        private SAXParser _parser;
297    
298        private SAXParserFactory _parserFactory = SAXParserFactory.newInstance();
299    
300        /**
301         * @since 3.0
302         */
303    
304        private final ClassResolver _resolver;
305    
306        /** @since 4.0 */
307    
308        private BindingSource _bindingSource;
309    
310        /**
311         * The root object parsed: a component or page specification, a library specification, or an
312         * application specification.
313         */
314        private Object _rootObject;
315    
316        /** @since 4.0 */
317    
318        private ValueConverter _valueConverter;
319    
320        // Identify all the different acceptible values.
321        // We continue to sneak by with a single map because
322        // there aren't conflicts; when we have 'foo' meaning
323        // different things in different places in the DTD, we'll
324        // need multiple maps.
325    
326        {
327    
328            CONVERSION_MAP.put("true", Boolean.TRUE);
329            CONVERSION_MAP.put("t", Boolean.TRUE);
330            CONVERSION_MAP.put("1", Boolean.TRUE);
331            CONVERSION_MAP.put("y", Boolean.TRUE);
332            CONVERSION_MAP.put("yes", Boolean.TRUE);
333            CONVERSION_MAP.put("on", Boolean.TRUE);
334            CONVERSION_MAP.put("aye", Boolean.TRUE);
335    
336            CONVERSION_MAP.put("false", Boolean.FALSE);
337            CONVERSION_MAP.put("f", Boolean.FALSE);
338            CONVERSION_MAP.put("0", Boolean.FALSE);
339            CONVERSION_MAP.put("off", Boolean.FALSE);
340            CONVERSION_MAP.put("no", Boolean.FALSE);
341            CONVERSION_MAP.put("n", Boolean.FALSE);
342            CONVERSION_MAP.put("nay", Boolean.FALSE);
343    
344            CONVERSION_MAP.put("none", BeanLifecycle.NONE);
345            CONVERSION_MAP.put("request", BeanLifecycle.REQUEST);
346            CONVERSION_MAP.put("page", BeanLifecycle.PAGE);
347            CONVERSION_MAP.put("render", BeanLifecycle.RENDER);
348    
349            _parserFactory.setNamespaceAware(false);
350            _parserFactory.setValidating(true);
351        }
352    
353        /**
354         * This constructor is a convienience used by some tests.
355         */
356        public SpecificationParser(ClassResolver resolver)
357        {
358            this(resolver, new SpecFactory());
359        }
360    
361        /**
362         * Create a new instance with resolver and a provided SpecFactory (used by Spindle).
363         * 
364         * @deprecated to be removed in release 4.1
365         */
366        public SpecificationParser(ClassResolver resolver, SpecFactory factory)
367        {
368            this(new DefaultErrorHandler(), LogFactory.getLog(SpecificationParser.class), resolver,
369                    factory);
370        }
371    
372        /**
373         * The full constructor, used within Tapestry.
374         */
375        public SpecificationParser(ErrorHandler errorHandler, Log log, ClassResolver resolver,
376                SpecFactory factory)
377        {
378            _errorHandler = errorHandler;
379            _log = log;
380            _resolver = resolver;
381            _factory = factory;
382        }
383    
384        protected void begin(String elementName, Map attributes)
385        {
386            _elementName = elementName;
387            _attributes = attributes;
388    
389            switch (getState())
390            {
391                case STATE_COMPONENT_SPECIFICATION_INITIAL:
392    
393                    beginComponentSpecificationInitial();
394                    break;
395    
396                case STATE_PAGE_SPECIFICATION_INITIAL:
397    
398                    beginPageSpecificationInitial();
399                    break;
400    
401                case STATE_APPLICATION_SPECIFICATION_INITIAL:
402    
403                    beginApplicationSpecificationInitial();
404                    break;
405    
406                case STATE_LIBRARY_SPECIFICATION_INITIAL:
407    
408                    beginLibrarySpecificationInitial();
409                    break;
410    
411                case STATE_COMPONENT_SPECIFICATION:
412    
413                    beginComponentSpecification();
414                    break;
415    
416                case STATE_PAGE_SPECIFICATION:
417    
418                    beginPageSpecification();
419                    break;
420    
421                case STATE_ALLOW_DESCRIPTION:
422    
423                    beginAllowDescription();
424                    break;
425    
426                case STATE_ALLOW_PROPERTY:
427    
428                    allowMetaData();
429                    break;
430    
431                case STATE_BEAN:
432    
433                    beginBean();
434                    break;
435    
436                case STATE_COMPONENT:
437    
438                    beginComponent();
439                    break;
440    
441                case STATE_LIBRARY_SPECIFICATION:
442    
443                    beginLibrarySpecification();
444                    break;
445    
446                case STATE_EXTENSION:
447    
448                    beginExtension();
449                    break;
450    
451                default:
452    
453                    unexpectedElement(_elementName);
454            }
455        }
456    
457        /**
458         * Special state for a number of specification types that can support the &lt;description&gt;
459         * element.
460         */
461    
462        private void beginAllowDescription()
463        {
464            if (_elementName.equals("description"))
465            {
466                enterDescription();
467                return;
468            }
469    
470            unexpectedElement(_elementName);
471        }
472    
473        /**
474         * Special state for a number of elements that can support the nested &lt;meta&gt; meta data
475         * element (&lt;property&gt; in 3.0 DTD).
476         */
477    
478        private void allowMetaData()
479        {
480            if (_DTD_4_0)
481            {
482                if (_elementName.equals("meta"))
483                {
484                    enterMeta();
485                    return;
486                }
487            }
488            else if (_elementName.equals("property"))
489            {
490                enterProperty_3_0();
491                return;
492            }
493    
494            unexpectedElement(_elementName);
495        }
496    
497        private void beginApplicationSpecificationInitial()
498        {
499            expectElement("application");
500    
501            String name = getAttribute("name");
502            String engineClassName = getAttribute("engine-class");
503    
504            IApplicationSpecification as = _factory.createApplicationSpecification();
505    
506            as.setName(name);
507    
508            if (HiveMind.isNonBlank(engineClassName))
509                as.setEngineClassName(engineClassName);
510    
511            _rootObject = as;
512    
513            push(_elementName, as, STATE_LIBRARY_SPECIFICATION);
514        }
515    
516        private void beginBean()
517        {
518            if (_elementName.equals("set"))
519            {
520                enterSet();
521                return;
522            }
523    
524            if (_elementName.equals("set-property"))
525            {
526                enterSetProperty_3_0();
527                return;
528            }
529    
530            if (_elementName.equals("set-message-property"))
531            {
532                enterSetMessage_3_0();
533                return;
534            }
535    
536            if (_elementName.equals("description"))
537            {
538                enterDescription();
539                return;
540            }
541    
542            allowMetaData();
543        }
544    
545        private void beginComponent()
546        {
547            // <binding> has changed between 3.0 and 4.0
548    
549            if (_elementName.equals("binding"))
550            {
551                enterBinding();
552                return;
553            }
554    
555            if (_elementName.equals("static-binding"))
556            {
557                enterStaticBinding_3_0();
558                return;
559            }
560    
561            if (_elementName.equals("message-binding"))
562            {
563                enterMessageBinding_3_0();
564                return;
565            }
566    
567            if (_elementName.equals("inherited-binding"))
568            {
569                enterInheritedBinding_3_0();
570                return;
571            }
572    
573            if (_elementName.equals("listener-binding"))
574            {
575                enterListenerBinding();
576                return;
577            }
578    
579            allowMetaData();
580        }
581    
582        private void beginComponentSpecification()
583        {
584            if (_elementName.equals("reserved-parameter"))
585            {
586                enterReservedParameter();
587                return;
588            }
589    
590            if (_elementName.equals("parameter"))
591            {
592                enterParameter();
593                return;
594            }
595    
596            // The remainder are common to both <component-specification> and
597            // <page-specification>
598    
599            beginPageSpecification();
600        }
601    
602        private void beginComponentSpecificationInitial()
603        {
604            expectElement("component-specification");
605    
606            IComponentSpecification cs = _factory.createComponentSpecification();
607    
608            cs.setAllowBody(getBooleanAttribute("allow-body", true));
609            cs.setAllowInformalParameters(getBooleanAttribute("allow-informal-parameters", true));
610            cs.setDeprecated(getBooleanAttribute("deprecated", false));
611    
612            String className = getAttribute("class");
613    
614            if (className != null)
615                cs.setComponentClassName(className);
616    
617            cs.setSpecificationLocation(getResource());
618    
619            _rootObject = cs;
620    
621            push(_elementName, cs, STATE_COMPONENT_SPECIFICATION);
622        }
623    
624        private void beginExtension()
625        {
626            if (_elementName.equals("configure"))
627            {
628                enterConfigure();
629                return;
630            }
631    
632            allowMetaData();
633        }
634    
635        private void beginLibrarySpecification()
636        {
637            if (_elementName.equals("description"))
638            {
639                enterDescription();
640                return;
641            }
642    
643            if (_elementName.equals("page"))
644            {
645                enterPage();
646                return;
647            }
648    
649            if (_elementName.equals("component-type"))
650            {
651                enterComponentType();
652                return;
653            }
654    
655            // Holdover from the 3.0 DTD, now ignored.
656    
657            if (_elementName.equals("service"))
658            {
659                enterService_3_0();
660                return;
661            }
662    
663            if (_elementName.equals("library"))
664            {
665                enterLibrary();
666                return;
667            }
668    
669            if (_elementName.equals("extension"))
670            {
671                enterExtension();
672                return;
673            }
674    
675            allowMetaData();
676        }
677    
678        private void beginLibrarySpecificationInitial()
679        {
680            expectElement("library-specification");
681    
682            ILibrarySpecification ls = _factory.createLibrarySpecification();
683    
684            _rootObject = ls;
685    
686            push(_elementName, ls, STATE_LIBRARY_SPECIFICATION);
687        }
688    
689        private void beginPageSpecification()
690        {
691            if (_elementName.equals("component"))
692            {
693                enterComponent();
694                return;
695            }
696    
697            if (_elementName.equals("bean"))
698            {
699                enterBean();
700                return;
701            }
702    
703            // <property-specification> in 3.0, <property> in 4.0
704            // Have to be careful, because <meta> in 4.0 was <property> in 3.0
705    
706            if (_elementName.equals("property-specification")
707                    || (_DTD_4_0 && _elementName.equals("property")))
708            {
709                enterProperty();
710                return;
711            }
712    
713            if (_elementName.equals("inject"))
714            {
715                enterInject();
716                return;
717            }
718    
719            // <asset> is new in 4.0
720    
721            if (_elementName.equals("asset"))
722            {
723                enterAsset();
724                return;
725            }
726    
727            // <context-asset>, <external-asset>, and <private-asset>
728            // are all throwbacks to the 3.0 DTD and don't exist
729            // in the 4.0 DTD.
730    
731            if (_elementName.equals("context-asset"))
732            {
733                enterContextAsset_3_0();
734                return;
735            }
736    
737            if (_elementName.equals("private-asset"))
738            {
739                enterPrivateAsset_3_0();
740                return;
741            }
742    
743            if (_elementName.equals("external-asset"))
744            {
745                enterExternalAsset_3_0();
746                return;
747    
748            }
749    
750            if (_elementName.equals("description"))
751            {
752                enterDescription();
753                return;
754            }
755    
756            allowMetaData();
757        }
758    
759        private void beginPageSpecificationInitial()
760        {
761            expectElement("page-specification");
762    
763            IComponentSpecification cs = _factory.createComponentSpecification();
764    
765            String className = getAttribute("class");
766    
767            if (className != null)
768                cs.setComponentClassName(className);
769    
770            cs.setSpecificationLocation(getResource());
771            cs.setPageSpecification(true);
772    
773            _rootObject = cs;
774    
775            push(_elementName, cs, STATE_PAGE_SPECIFICATION);
776        }
777    
778        /**
779         * Close a stream (if not null), ignoring any errors.
780         */
781        private void close(InputStream stream)
782        {
783            try
784            {
785                if (stream != null)
786                    stream.close();
787            }
788            catch (IOException ex)
789            {
790                // ignore
791            }
792        }
793    
794        private void copyBindings(String sourceComponentId, IComponentSpecification cs,
795                IContainedComponent target)
796        {
797            IContainedComponent source = cs.getComponent(sourceComponentId);
798            if (source == null)
799                throw new DocumentParseException(ParseMessages.unableToCopy(sourceComponentId),
800                        getLocation());
801    
802            Iterator i = source.getBindingNames().iterator();
803            while (i.hasNext())
804            {
805                String bindingName = (String) i.next();
806                IBindingSpecification binding = source.getBinding(bindingName);
807                target.setBinding(bindingName, binding);
808            }
809    
810            target.setType(source.getType());
811        }
812    
813        protected void end(String elementName)
814        {
815            _elementName = elementName;
816    
817            switch (getState())
818            {
819                case STATE_DESCRIPTION:
820    
821                    endDescription();
822                    break;
823    
824                case STATE_META:
825    
826                    endProperty();
827                    break;
828    
829                case STATE_SET:
830    
831                    endSetProperty();
832                    break;
833    
834                case STATE_BINDING_3_0:
835    
836                    endBinding_3_0();
837                    break;
838    
839                case STATE_BINDING:
840    
841                    endBinding();
842                    break;
843    
844                case STATE_LISTENER_BINDING:
845    
846                    endListenerBinding();
847                    break;
848    
849                case STATE_STATIC_BINDING:
850    
851                    endStaticBinding();
852                    break;
853    
854                case STATE_PROPERTY:
855    
856                    endPropertySpecification();
857                    break;
858    
859                case STATE_LIBRARY_SPECIFICATION:
860    
861                    endLibrarySpecification();
862                    break;
863    
864                case STATE_CONFIGURE:
865    
866                    endConfigure();
867                    break;
868    
869                default:
870                    break;
871            }
872    
873            // Pop the top element of the stack and continue processing from there.
874    
875            pop();
876        }
877    
878        private void endBinding_3_0()
879        {
880            BindingSetter bs = (BindingSetter) peekObject();
881    
882            String expression = getExtendedValue(bs.getValue(), "expression", true);
883    
884            IBindingSpecification spec = _factory.createBindingSpecification();
885    
886            spec.setType(BindingType.PREFIXED);
887            spec.setValue(BindingConstants.OGNL_PREFIX + ":" + expression);
888    
889            bs.apply(spec);
890        }
891    
892        private void endConfigure()
893        {
894            ExtensionConfigurationSetter setter = (ExtensionConfigurationSetter) peekObject();
895    
896            String finalValue = getExtendedValue(setter.getValue(), "value", true);
897    
898            setter.apply(finalValue);
899        }
900    
901        private void endDescription()
902        {
903            DescriptionSetter setter = (DescriptionSetter) peekObject();
904    
905            String description = peekContent();
906    
907            setter.apply(description);
908        }
909    
910        private void endLibrarySpecification()
911        {
912            ILibrarySpecification spec = (ILibrarySpecification) peekObject();
913    
914            spec.setSpecificationLocation(getResource());
915    
916            spec.instantiateImmediateExtensions();
917        }
918    
919        private void endListenerBinding()
920        {
921            BindingSetter bs = (BindingSetter) peekObject();
922    
923            IListenerBindingSpecification lbs = _factory.createListenerBindingSpecification();
924    
925            lbs.setLanguage(bs.getValue());
926    
927            // Do we need a check for no body content?
928    
929            lbs.setValue(peekContent());
930            lbs.setLocation(getLocation());
931    
932            bs.apply(lbs);
933        }
934    
935        private void endProperty()
936        {
937            PropertyValueSetter pvs = (PropertyValueSetter) peekObject();
938    
939            String finalValue = getExtendedValue(pvs.getPropertyValue(), "value", true);
940    
941            pvs.applyValue(finalValue);
942        }
943    
944        private void endPropertySpecification()
945        {
946            IPropertySpecification ps = (IPropertySpecification) peekObject();
947    
948            String initialValue = getExtendedValue(ps.getInitialValue(), "initial-value", false);
949    
950            // In the 3.0 DTD, the initial value was always an OGNL expression.
951            // In the 4.0 DTD, it is a binding reference, qualified with a prefix.
952    
953            if (initialValue != null && !_DTD_4_0)
954                initialValue = BindingConstants.OGNL_PREFIX + ":" + initialValue;
955    
956            ps.setInitialValue(initialValue);
957        }
958    
959        private void endSetProperty()
960        {
961            BeanSetPropertySetter bs = (BeanSetPropertySetter) peekObject();
962    
963            String finalValue = getExtendedValue(bs.getBindingReference(), "expression", true);
964    
965            bs.applyBindingReference(finalValue);
966        }
967    
968        private void endStaticBinding()
969        {
970            BindingSetter bs = (BindingSetter) peekObject();
971    
972            String literalValue = getExtendedValue(bs.getValue(), "value", true);
973    
974            IBindingSpecification spec = _factory.createBindingSpecification();
975    
976            spec.setType(BindingType.PREFIXED);
977            spec.setValue(BindingConstants.LITERAL_PREFIX + ":" + literalValue);
978    
979            bs.apply(spec);
980        }
981    
982        private void enterAsset(String pathAttributeName, String prefix)
983        {
984            String name = getValidatedAttribute("name", ASSET_NAME_PATTERN, "invalid-asset-name");
985            String path = getAttribute(pathAttributeName);
986            String propertyName = getValidatedAttribute(
987                    "property",
988                    PROPERTY_NAME_PATTERN,
989                    "invalid-property-name");
990    
991            IAssetSpecification ia = _factory.createAssetSpecification();
992    
993            ia.setPath(prefix == null ? path : prefix + path);
994            ia.setPropertyName(propertyName);
995    
996            IComponentSpecification cs = (IComponentSpecification) peekObject();
997    
998            cs.addAsset(name, ia);
999    
1000            push(_elementName, ia, STATE_ALLOW_PROPERTY);
1001        }
1002    
1003        private void enterBean()
1004        {
1005            String name = getValidatedAttribute("name", BEAN_NAME_PATTERN, "invalid-bean-name");
1006    
1007            String classAttribute = getAttribute("class");
1008    
1009            // Look for the lightweight initialization
1010    
1011            int commax = classAttribute.indexOf(',');
1012    
1013            String className = commax < 0 ? classAttribute : classAttribute.substring(0, commax);
1014    
1015            BeanLifecycle lifecycle = (BeanLifecycle) getConvertedAttribute(
1016                    "lifecycle",
1017                    BeanLifecycle.REQUEST);
1018            String propertyName = getValidatedAttribute(
1019                    "property",
1020                    PROPERTY_NAME_PATTERN,
1021                    "invalid-property-name");
1022    
1023            IBeanSpecification bs = _factory.createBeanSpecification();
1024    
1025            bs.setClassName(className);
1026            bs.setLifecycle(lifecycle);
1027            bs.setPropertyName(propertyName);
1028    
1029            if (commax > 0)
1030            {
1031                String initializer = classAttribute.substring(commax + 1);
1032                bs.addInitializer(new LightweightBeanInitializer(initializer));
1033            }
1034    
1035            IComponentSpecification cs = (IComponentSpecification) peekObject();
1036    
1037            cs.addBeanSpecification(name, bs);
1038    
1039            push(_elementName, bs, STATE_BEAN);
1040        }
1041    
1042        private void enterBinding()
1043        {
1044            if (!_DTD_4_0)
1045            {
1046                enterBinding_3_0();
1047                return;
1048            }
1049    
1050            // 4.0 stuff
1051    
1052            String name = getValidatedAttribute(
1053                    "name",
1054                    PARAMETER_NAME_PATTERN,
1055                    "invalid-parameter-name");
1056            String value = getAttribute("value");
1057    
1058            IContainedComponent cc = (IContainedComponent) peekObject();
1059    
1060            BindingSetter bs = new BindingSetter(cc, name, value);
1061    
1062            push(_elementName, bs, STATE_BINDING, false);
1063        }
1064    
1065        private void endBinding()
1066        {
1067            BindingSetter bs = (BindingSetter) peekObject();
1068    
1069            String value = getExtendedValue(bs.getValue(), "value", true);
1070    
1071            IBindingSpecification spec = _factory.createBindingSpecification();
1072    
1073            spec.setType(BindingType.PREFIXED);
1074            spec.setValue(value);
1075    
1076            bs.apply(spec);
1077        }
1078    
1079        /**
1080         * Handles a binding in a 3.0 DTD.
1081         */
1082    
1083        private void enterBinding_3_0()
1084        {
1085            String name = getAttribute("name");
1086            String expression = getAttribute("expression");
1087    
1088            IContainedComponent cc = (IContainedComponent) peekObject();
1089    
1090            BindingSetter bs = new BindingSetter(cc, name, expression);
1091    
1092            push(_elementName, bs, STATE_BINDING_3_0, false);
1093        }
1094    
1095        private void enterComponent()
1096        {
1097            String id = getValidatedAttribute("id", COMPONENT_ID_PATTERN, "invalid-component-id");
1098    
1099            String type = getValidatedAttribute(
1100                    "type",
1101                    COMPONENT_TYPE_PATTERN,
1102                    "invalid-component-type");
1103            String copyOf = getAttribute("copy-of");
1104            boolean inherit = getBooleanAttribute("inherit-informal-parameters", false);
1105            String propertyName = getValidatedAttribute(
1106                    "property",
1107                    PROPERTY_NAME_PATTERN,
1108                    "invalid-property-name");
1109    
1110            // Check that either copy-of or type, but not both
1111    
1112            boolean hasCopyOf = HiveMind.isNonBlank(copyOf);
1113    
1114            if (hasCopyOf)
1115            {
1116                if (HiveMind.isNonBlank(type))
1117                    throw new DocumentParseException(ParseMessages.bothTypeAndCopyOf(id), getLocation());
1118            }
1119            else
1120            {
1121                if (HiveMind.isBlank(type))
1122                    throw new DocumentParseException(ParseMessages.missingTypeOrCopyOf(id),
1123                            getLocation());
1124            }
1125    
1126            IContainedComponent cc = _factory.createContainedComponent();
1127            cc.setType(type);
1128            cc.setCopyOf(copyOf);
1129            cc.setInheritInformalParameters(inherit);
1130            cc.setPropertyName(propertyName);
1131    
1132            IComponentSpecification cs = (IComponentSpecification) peekObject();
1133    
1134            cs.addComponent(id, cc);
1135    
1136            if (hasCopyOf)
1137                copyBindings(copyOf, cs, cc);
1138    
1139            push(_elementName, cc, STATE_COMPONENT);
1140        }
1141    
1142        private void enterComponentType()
1143        {
1144            String type = getValidatedAttribute(
1145                    "type",
1146                    COMPONENT_ALIAS_PATTERN,
1147                    "invalid-component-type");
1148            String path = getAttribute("specification-path");
1149    
1150            ILibrarySpecification ls = (ILibrarySpecification) peekObject();
1151    
1152            ls.setComponentSpecificationPath(type, path);
1153    
1154            push(_elementName, null, STATE_NO_CONTENT);
1155        }
1156    
1157        private void enterConfigure()
1158        {
1159            String attributeName = _DTD_4_0 ? "property" : "property-name";
1160    
1161            String propertyName = getValidatedAttribute(
1162                    attributeName,
1163                    PROPERTY_NAME_PATTERN,
1164                    "invalid-property-name");
1165    
1166            String value = getAttribute("value");
1167    
1168            IExtensionSpecification es = (IExtensionSpecification) peekObject();
1169    
1170            ExtensionConfigurationSetter setter = new ExtensionConfigurationSetter(es, propertyName,
1171                    value);
1172    
1173            push(_elementName, setter, STATE_CONFIGURE, false);
1174        }
1175    
1176        private void enterContextAsset_3_0()
1177        {
1178            enterAsset("path", "context:");
1179        }
1180    
1181        /**
1182         * New in the 4.0 DTD. When using the 4.0 DTD, you must explicitly specify prefix if the asset
1183         * is not stored in the same domain as the specification file.
1184         * 
1185         * @since 4.0
1186         */
1187    
1188        private void enterAsset()
1189        {
1190            enterAsset("path", null);
1191        }
1192    
1193        private void enterDescription()
1194        {
1195            push(_elementName, new DescriptionSetter(peekObject()), STATE_DESCRIPTION, false);
1196        }
1197    
1198        private void enterExtension()
1199        {
1200            String name = getValidatedAttribute(
1201                    "name",
1202                    EXTENSION_NAME_PATTERN,
1203                    "invalid-extension-name");
1204    
1205            boolean immediate = getBooleanAttribute("immediate", false);
1206            String className = getAttribute("class");
1207    
1208            IExtensionSpecification es = _factory.createExtensionSpecification(
1209                    _resolver,
1210                    _valueConverter);
1211    
1212            es.setClassName(className);
1213            es.setImmediate(immediate);
1214    
1215            ILibrarySpecification ls = (ILibrarySpecification) peekObject();
1216    
1217            ls.addExtensionSpecification(name, es);
1218    
1219            push(_elementName, es, STATE_EXTENSION);
1220        }
1221    
1222        private void enterExternalAsset_3_0()
1223        {
1224            // External URLs get no prefix, but will have a scheme (i.e., "http:") that
1225            // fulfils much the same purpose.
1226    
1227            enterAsset("URL", null);
1228        }
1229    
1230        /** A throwback to the 3.0 DTD */
1231    
1232        private void enterInheritedBinding_3_0()
1233        {
1234            String name = getAttribute("name");
1235            String parameterName = getAttribute("parameter-name");
1236    
1237            IBindingSpecification bs = _factory.createBindingSpecification();
1238            bs.setType(BindingType.INHERITED);
1239            bs.setValue(parameterName);
1240    
1241            IContainedComponent cc = (IContainedComponent) peekObject();
1242    
1243            cc.setBinding(name, bs);
1244    
1245            push(_elementName, null, STATE_NO_CONTENT);
1246        }
1247    
1248        private void enterLibrary()
1249        {
1250            String libraryId = getValidatedAttribute("id", LIBRARY_ID_PATTERN, "invalid-library-id");
1251            String path = getAttribute("specification-path");
1252    
1253            if (libraryId.equals(INamespace.FRAMEWORK_NAMESPACE)
1254                    || libraryId.equals(INamespace.APPLICATION_NAMESPACE))
1255                throw new DocumentParseException(ParseMessages
1256                        .frameworkLibraryIdIsReserved(INamespace.FRAMEWORK_NAMESPACE), getLocation());
1257    
1258            ILibrarySpecification ls = (ILibrarySpecification) peekObject();
1259    
1260            ls.setLibrarySpecificationPath(libraryId, path);
1261    
1262            push(_elementName, null, STATE_NO_CONTENT);
1263        }
1264    
1265        private void enterListenerBinding()
1266        {
1267            String name = getAttribute("name");
1268            String language = getAttribute("language");
1269    
1270            IContainedComponent cc = (IContainedComponent) peekObject();
1271            BindingSetter bs = new BindingSetter(cc, name, language);
1272    
1273            push(_elementName, bs, STATE_LISTENER_BINDING, false);
1274        }
1275    
1276        private void enterMessageBinding_3_0()
1277        {
1278            String name = getAttribute("name");
1279            String key = getAttribute("key");
1280    
1281            IBindingSpecification bs = _factory.createBindingSpecification();
1282            bs.setType(BindingType.PREFIXED);
1283            bs.setValue(BindingConstants.MESSAGE_PREFIX + ":" + key);
1284            bs.setLocation(getLocation());
1285    
1286            IContainedComponent cc = (IContainedComponent) peekObject();
1287    
1288            cc.setBinding(name, bs);
1289    
1290            push(_elementName, null, STATE_NO_CONTENT);
1291        }
1292    
1293        private void enterPage()
1294        {
1295            String name = getValidatedAttribute("name", PAGE_NAME_PATTERN, "invalid-page-name");
1296            String path = getAttribute("specification-path");
1297    
1298            ILibrarySpecification ls = (ILibrarySpecification) peekObject();
1299    
1300            ls.setPageSpecificationPath(name, path);
1301    
1302            push(_elementName, null, STATE_NO_CONTENT);
1303        }
1304    
1305        private void enterParameter()
1306        {
1307            IParameterSpecification ps = _factory.createParameterSpecification();
1308    
1309            String name = getValidatedAttribute(
1310                    "name",
1311                    PARAMETER_NAME_PATTERN,
1312                    "invalid-parameter-name");
1313    
1314            String attributeName = _DTD_4_0 ? "property" : "property-name";
1315    
1316            String propertyName = getValidatedAttribute(
1317                    attributeName,
1318                    PROPERTY_NAME_PATTERN,
1319                    "invalid-property-name");
1320    
1321            if (propertyName == null)
1322                propertyName = name;
1323    
1324            ps.setParameterName(name);
1325            ps.setPropertyName(propertyName);
1326    
1327            ps.setRequired(getBooleanAttribute("required", false));
1328    
1329            // In the 3.0 DTD, default-value was always an OGNL expression.
1330            // Starting with 4.0, it's like a binding (prefixed). For a 3.0
1331            // DTD, we supply the "ognl:" prefix.
1332    
1333            String defaultValue = getAttribute("default-value");
1334    
1335            if (defaultValue != null && !_DTD_4_0)
1336                defaultValue = BindingConstants.OGNL_PREFIX + ":" + defaultValue;
1337    
1338            ps.setDefaultValue(defaultValue);
1339    
1340            if (!_DTD_4_0)
1341            {
1342                // When direction=auto (in a 3.0 DTD), turn caching off
1343    
1344                String direction = getAttribute("direction");
1345                ps.setCache(!"auto".equals(direction));
1346            }
1347            else
1348            {
1349                boolean cache = getBooleanAttribute("cache", true);
1350                ps.setCache(cache);
1351            }
1352    
1353            // type will only be specified in a 3.0 DTD.
1354    
1355            String type = getAttribute("type");
1356    
1357            if (type != null)
1358                ps.setType(type);
1359    
1360            // aliases is new in the 4.0 DTD
1361    
1362            String aliases = getAttribute("aliases");
1363    
1364            ps.setAliases(aliases);
1365            ps.setDeprecated(getBooleanAttribute("deprecated", false));
1366    
1367            IComponentSpecification cs = (IComponentSpecification) peekObject();
1368    
1369            cs.addParameter(ps);
1370    
1371            push(_elementName, ps, STATE_ALLOW_DESCRIPTION);
1372        }
1373    
1374        private void enterPrivateAsset_3_0()
1375        {
1376            enterAsset("resource-path", "classpath:");
1377        }
1378    
1379        /** @since 4.0 */
1380        private void enterMeta()
1381        {
1382            String key = getAttribute("key");
1383            String value = getAttribute("value");
1384    
1385            // Value may be null, in which case the value is set from the element content
1386    
1387            IPropertyHolder ph = (IPropertyHolder) peekObject();
1388    
1389            push(_elementName, new PropertyValueSetter(ph, key, value), STATE_META, false);
1390        }
1391    
1392        private void enterProperty_3_0()
1393        {
1394            String name = getAttribute("name");
1395            String value = getAttribute("value");
1396    
1397            // Value may be null, in which case the value is set from the element content
1398    
1399            IPropertyHolder ph = (IPropertyHolder) peekObject();
1400    
1401            push(_elementName, new PropertyValueSetter(ph, name, value), STATE_META, false);
1402        }
1403    
1404        /**
1405         * &tl;property&gt; in 4.0, or &lt;property-specification&gt; in 3.0
1406         */
1407    
1408        private void enterProperty()
1409        {
1410            String name = getValidatedAttribute("name", PROPERTY_NAME_PATTERN, "invalid-property-name");
1411            String type = getAttribute("type");
1412    
1413            String persistence = null;
1414    
1415            if (_DTD_4_0)
1416                persistence = getAttribute("persist");
1417            else
1418                persistence = getBooleanAttribute("persistent", false) ? "session" : null;
1419    
1420            String initialValue = getAttribute("initial-value");
1421    
1422            IPropertySpecification ps = _factory.createPropertySpecification();
1423            ps.setName(name);
1424    
1425            if (HiveMind.isNonBlank(type))
1426                ps.setType(type);
1427    
1428            ps.setPersistence(persistence);
1429            ps.setInitialValue(initialValue);
1430    
1431            IComponentSpecification cs = (IComponentSpecification) peekObject();
1432            cs.addPropertySpecification(ps);
1433    
1434            push(_elementName, ps, STATE_PROPERTY, false);
1435        }
1436    
1437        /**
1438         * @since 4.0
1439         */
1440    
1441        private void enterInject()
1442        {
1443            String property = getValidatedAttribute(
1444                    "property",
1445                    PROPERTY_NAME_PATTERN,
1446                    "invalid-property-name");
1447            String type = getAttribute("type");
1448            String objectReference = getAttribute("object");
1449    
1450            InjectSpecification spec = _factory.createInjectSpecification();
1451    
1452            spec.setProperty(property);
1453            spec.setType(type);
1454            spec.setObject(objectReference);
1455            IComponentSpecification cs = (IComponentSpecification) peekObject();
1456    
1457            cs.addInjectSpecification(spec);
1458    
1459            push(_elementName, spec, STATE_NO_CONTENT);
1460        }
1461    
1462        private void enterReservedParameter()
1463        {
1464            String name = getAttribute("name");
1465            IComponentSpecification cs = (IComponentSpecification) peekObject();
1466    
1467            cs.addReservedParameterName(name);
1468    
1469            push(_elementName, null, STATE_NO_CONTENT);
1470        }
1471    
1472        private void enterService_3_0()
1473        {
1474            _errorHandler.error(_log, ParseMessages.serviceElementNotSupported(), getLocation(), null);
1475    
1476            push(_elementName, null, STATE_NO_CONTENT);
1477        }
1478    
1479        private void enterSetMessage_3_0()
1480        {
1481            String name = getAttribute("name");
1482            String key = getAttribute("key");
1483    
1484            BindingBeanInitializer bi = _factory.createBindingBeanInitializer(_bindingSource);
1485    
1486            bi.setPropertyName(name);
1487            bi.setBindingReference(BindingConstants.MESSAGE_PREFIX + ":" + key);
1488            bi.setLocation(getLocation());
1489    
1490            IBeanSpecification bs = (IBeanSpecification) peekObject();
1491    
1492            bs.addInitializer(bi);
1493    
1494            push(_elementName, null, STATE_NO_CONTENT);
1495        }
1496    
1497        private void enterSet()
1498        {
1499            String name = getAttribute("name");
1500            String reference = getAttribute("value");
1501    
1502            BindingBeanInitializer bi = _factory.createBindingBeanInitializer(_bindingSource);
1503    
1504            bi.setPropertyName(name);
1505    
1506            IBeanSpecification bs = (IBeanSpecification) peekObject();
1507    
1508            push(_elementName, new BeanSetPropertySetter(bs, bi, null, reference), STATE_SET, false);
1509        }
1510    
1511        private void enterSetProperty_3_0()
1512        {
1513            String name = getAttribute("name");
1514            String expression = getAttribute("expression");
1515    
1516            BindingBeanInitializer bi = _factory.createBindingBeanInitializer(_bindingSource);
1517    
1518            bi.setPropertyName(name);
1519    
1520            IBeanSpecification bs = (IBeanSpecification) peekObject();
1521    
1522            push(_elementName, new BeanSetPropertySetter(bs, bi, BindingConstants.OGNL_PREFIX + ":",
1523                    expression), STATE_SET, false);
1524        }
1525    
1526        private void enterStaticBinding_3_0()
1527        {
1528            String name = getAttribute("name");
1529            String expression = getAttribute("value");
1530    
1531            IContainedComponent cc = (IContainedComponent) peekObject();
1532    
1533            BindingSetter bs = new BindingSetter(cc, name, expression);
1534    
1535            push(_elementName, bs, STATE_STATIC_BINDING, false);
1536        }
1537    
1538        private void expectElement(String elementName)
1539        {
1540            if (_elementName.equals(elementName))
1541                return;
1542    
1543            throw new DocumentParseException(ParseMessages.incorrectDocumentType(
1544                    _elementName,
1545                    elementName), getLocation(), null);
1546    
1547        }
1548    
1549        private String getAttribute(String name)
1550        {
1551            return (String) _attributes.get(name);
1552        }
1553    
1554        private boolean getBooleanAttribute(String name, boolean defaultValue)
1555        {
1556            String value = getAttribute(name);
1557    
1558            if (value == null)
1559                return defaultValue;
1560    
1561            Boolean b = (Boolean) CONVERSION_MAP.get(value);
1562    
1563            return b.booleanValue();
1564        }
1565    
1566        private Object getConvertedAttribute(String name, Object defaultValue)
1567        {
1568            String key = getAttribute(name);
1569    
1570            if (key == null)
1571                return defaultValue;
1572    
1573            return CONVERSION_MAP.get(key);
1574        }
1575    
1576        private InputSource getDTDInputSource(String name)
1577        {
1578            InputStream stream = getClass().getResourceAsStream(name);
1579    
1580            return new InputSource(stream);
1581        }
1582    
1583        private String getExtendedValue(String attributeValue, String attributeName, boolean required)
1584        {
1585            String contentValue = peekContent();
1586    
1587            boolean asAttribute = HiveMind.isNonBlank(attributeValue);
1588            boolean asContent = HiveMind.isNonBlank(contentValue);
1589    
1590            if (asAttribute && asContent)
1591            {
1592                throw new DocumentParseException(ParseMessages.noAttributeAndBody(
1593                        attributeName,
1594                        _elementName), getLocation(), null);
1595            }
1596    
1597            if (required && !(asAttribute || asContent))
1598            {
1599                throw new DocumentParseException(ParseMessages.requiredExtendedAttribute(
1600                        _elementName,
1601                        attributeName), getLocation(), null);
1602            }
1603    
1604            if (asAttribute)
1605                return attributeValue;
1606    
1607            return contentValue;
1608        }
1609    
1610        private String getValidatedAttribute(String name, String pattern, String errorKey)
1611        {
1612            String value = getAttribute(name);
1613    
1614            if (value == null)
1615                return null;
1616    
1617            if (_matcher.matches(pattern, value))
1618                return value;
1619    
1620            throw new InvalidStringException(ParseMessages.invalidAttribute(errorKey, value), value,
1621                    getLocation());
1622        }
1623    
1624        protected void initializeParser(Resource resource, int startState)
1625        {
1626            super.initializeParser(resource, startState);
1627    
1628            _rootObject = null;
1629            _attributes = new HashMap();
1630        }
1631    
1632        public IApplicationSpecification parseApplicationSpecification(Resource resource)
1633        {
1634            initializeParser(resource, STATE_APPLICATION_SPECIFICATION_INITIAL);
1635    
1636            try
1637            {
1638                parseDocument();
1639    
1640                return (IApplicationSpecification) _rootObject;
1641            }
1642            finally
1643            {
1644                resetParser();
1645            }
1646        }
1647    
1648        public IComponentSpecification parseComponentSpecification(Resource resource)
1649        {
1650            initializeParser(resource, STATE_COMPONENT_SPECIFICATION_INITIAL);
1651    
1652            try
1653            {
1654                parseDocument();
1655    
1656                return (IComponentSpecification) _rootObject;
1657            }
1658            finally
1659            {
1660                resetParser();
1661            }
1662        }
1663    
1664        private void parseDocument()
1665        {
1666            InputStream stream = null;
1667    
1668            Resource resource = getResource();
1669    
1670            boolean success = false;
1671    
1672            try
1673            {
1674                if (_parser == null)
1675                    _parser = _parserFactory.newSAXParser();
1676    
1677                URL resourceURL = resource.getResourceURL();
1678    
1679                if (resourceURL == null)
1680                    throw new DocumentParseException(ParseMessages.missingResource(resource), resource);
1681    
1682                InputStream rawStream = resourceURL.openStream();
1683                stream = new BufferedInputStream(rawStream);
1684    
1685                _parser.parse(stream, this, resourceURL.toExternalForm());
1686    
1687                stream.close();
1688                stream = null;
1689    
1690                success = true;
1691            }
1692            catch (SAXParseException ex)
1693            {
1694                _parser = null;
1695    
1696                Location location = new LocationImpl(resource, ex.getLineNumber(), ex.getColumnNumber());
1697    
1698                throw new DocumentParseException(ParseMessages.errorReadingResource(resource, ex),
1699                        location, ex);
1700            }
1701            catch (Exception ex)
1702            {
1703                _parser = null;
1704    
1705                throw new DocumentParseException(ParseMessages.errorReadingResource(resource, ex),
1706                        resource, ex);
1707            }
1708            finally
1709            {
1710                if (!success)
1711                    _parser = null;
1712    
1713                close(stream);
1714            }
1715        }
1716    
1717        public ILibrarySpecification parseLibrarySpecification(Resource resource)
1718        {
1719            initializeParser(resource, STATE_LIBRARY_SPECIFICATION_INITIAL);
1720    
1721            try
1722            {
1723                parseDocument();
1724    
1725                return (ILibrarySpecification) _rootObject;
1726            }
1727            finally
1728            {
1729                resetParser();
1730            }
1731        }
1732    
1733        public IComponentSpecification parsePageSpecification(Resource resource)
1734        {
1735            initializeParser(resource, STATE_PAGE_SPECIFICATION_INITIAL);
1736    
1737            try
1738            {
1739                parseDocument();
1740    
1741                return (IComponentSpecification) _rootObject;
1742            }
1743            finally
1744            {
1745                resetParser();
1746            }
1747        }
1748    
1749        protected String peekContent()
1750        {
1751            String content = super.peekContent();
1752    
1753            if (content == null)
1754                return null;
1755    
1756            return content.trim();
1757        }
1758    
1759        protected void resetParser()
1760        {
1761            _rootObject = null;
1762            _DTD_4_0 = false;
1763    
1764            _attributes.clear();
1765        }
1766    
1767        /**
1768         * Resolved an external entity, which is assumed to be the doctype. Might need a check to ensure
1769         * that specs without a doctype fail.
1770         */
1771        public InputSource resolveEntity(String publicId, String systemId) throws SAXException
1772        {
1773            if (TAPESTRY_DTD_4_0_PUBLIC_ID.equals(publicId))
1774            {
1775                _DTD_4_0 = true;
1776                return getDTDInputSource("Tapestry_4_0.dtd");
1777            }
1778    
1779            if (TAPESTRY_DTD_3_0_PUBLIC_ID.equals(publicId))
1780                return getDTDInputSource("Tapestry_3_0.dtd");
1781    
1782            throw new DocumentParseException(ParseMessages.unknownPublicId(getResource(), publicId),
1783                    new LocationImpl(getResource()), null);
1784        }
1785    
1786        /** @since 4.0 */
1787        public void setBindingSource(BindingSource bindingSource)
1788        {
1789            _bindingSource = bindingSource;
1790        }
1791    
1792        /** @since 4.0 */
1793        public void setValueConverter(ValueConverter valueConverter)
1794        {
1795            _valueConverter = valueConverter;
1796        }
1797    }