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