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 }