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