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