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