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
015package org.apache.tapestry.parse;
016
017import java.io.BufferedInputStream;
018import java.io.IOException;
019import java.io.InputStream;
020import java.net.URL;
021import java.util.HashMap;
022import java.util.Iterator;
023import java.util.Map;
024
025import javax.xml.parsers.SAXParser;
026import javax.xml.parsers.SAXParserFactory;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.apache.hivemind.ClassResolver;
031import org.apache.hivemind.ErrorHandler;
032import org.apache.hivemind.HiveMind;
033import org.apache.hivemind.Location;
034import org.apache.hivemind.Resource;
035import org.apache.hivemind.impl.DefaultErrorHandler;
036import org.apache.hivemind.impl.LocationImpl;
037import org.apache.hivemind.parse.AbstractParser;
038import org.apache.tapestry.INamespace;
039import org.apache.tapestry.Tapestry;
040import org.apache.tapestry.bean.BindingBeanInitializer;
041import org.apache.tapestry.bean.LightweightBeanInitializer;
042import org.apache.tapestry.binding.BindingConstants;
043import org.apache.tapestry.binding.BindingSource;
044import org.apache.tapestry.coerce.ValueConverter;
045import org.apache.tapestry.spec.BeanLifecycle;
046import org.apache.tapestry.spec.BindingType;
047import org.apache.tapestry.spec.IApplicationSpecification;
048import org.apache.tapestry.spec.IAssetSpecification;
049import org.apache.tapestry.spec.IBeanSpecification;
050import org.apache.tapestry.spec.IBindingSpecification;
051import org.apache.tapestry.spec.IComponentSpecification;
052import org.apache.tapestry.spec.IContainedComponent;
053import org.apache.tapestry.spec.IExtensionSpecification;
054import org.apache.tapestry.spec.ILibrarySpecification;
055import org.apache.tapestry.spec.IParameterSpecification;
056import org.apache.tapestry.spec.IPropertySpecification;
057import org.apache.tapestry.spec.InjectSpecification;
058import org.apache.tapestry.spec.SpecFactory;
059import org.apache.tapestry.util.IPropertyHolder;
060import org.apache.tapestry.util.RegexpMatcher;
061import org.apache.tapestry.util.xml.DocumentParseException;
062import org.apache.tapestry.util.xml.InvalidStringException;
063import org.xml.sax.InputSource;
064import org.xml.sax.SAXException;
065import 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 */
074public 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 &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     * 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 &lt;page&gt; 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 &lt;service&gt; 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 &lt;description&gt;
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 &lt;meta&gt; meta data
474     * element (&lt;property&gt; 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&gt; in 4.0, or &lt;property-specification&gt; 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}