2009/05/20 - Apache Shale has been retired.
For more information, please explore the Attic.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.shale.clay.config.beans;
22
23 import java.io.IOException;
24 import java.net.URL;
25 import java.net.URLConnection;
26 import java.util.ArrayList;
27 import java.util.Enumeration;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Stack;
33 import java.util.StringTokenizer;
34 import java.util.TreeMap;
35 import java.util.TreeSet;
36
37 import javax.servlet.ServletContext;
38
39 import org.apache.commons.logging.Log;
40 import org.apache.commons.logging.LogFactory;
41 import org.apache.shale.clay.config.ClayConfigParser;
42 import org.apache.shale.clay.config.ClayXmlParser;
43 import org.apache.shale.clay.config.Globals;
44 import org.apache.shale.util.Messages;
45 import org.xml.sax.SAXException;
46
47 /***
48 * <p>This class is kind of the metadata object pool for configuration data
49 * loaded from XML files on startup in the {@link org.apache.shale.clay.config.ClayConfigureListener}
50 * by the {@link org.apache.shale.clay.config.ClayXmlParser}. An instance of this
51 * class will be registered with the {@link ConfigBeanFactory}.
52 * </p>
53 */
54 public class ComponentConfigBean implements ConfigBean {
55
56 /***
57 * <p>Commons logger.</p>
58 */
59 private static Log log;
60 static {
61 log = LogFactory.getLog(ComponentConfigBean.class);
62 }
63
64 /***
65 * <p>Uses the digester to load the configuration files
66 * into a object graph cached in <code>displayElements</code>.
67 * </p>
68 */
69 protected ClayConfigParser parser = null;
70
71 /***
72 * <p>This parameter is initialized from the <code>init</code>
73 * method from the <code>org.apache.shale.clay.AUTO_RELOAD_CONFIG_FILES</code> init
74 * parameter in the web.xml. The default value is <code>true</code>
75 * which will trigger reloading the files when a change has occurred.
76 * </p>
77 */
78 protected boolean isWatchDogOn = true;
79
80
81 /***
82 * <p>Map of {@link WatchDog} that watches the configuration files looking for changes.
83 * The configuration files are defined by the {@link ConfigBean.ConfigDefinition} top level
84 * interface.</p>
85 */
86 protected Map watchDogs = null;
87
88 /***
89 * <p>
90 * Message resources for this class.
91 * </p>
92 */
93 protected static Messages messages = new Messages(
94 "org.apache.shale.clay.Bundle", ComponentConfigBean.class
95 .getClassLoader());
96
97 /***
98 * <p>The suffixes used to identify that a jsfid is a template style of
99 * composition. If it has a matching suffix, it will be handled
100 * by the {@link TemplateConfigBean} or {@link TemplateComponentConfigBean};
101 * Otherwise, it's handled by {@link ComponentConfigBean}.</p>
102 */
103 protected String[] suffixes = null;
104
105 /***
106 * <p>Reference to the <code>ServletContext</code>.</p>
107 */
108 protected transient ServletContext context = null;
109
110
111 /***
112 * <p>Flag that indicates the current mode is design time.
113 * In design time mode, the descriptions in the clay
114 * configuration files will populate the <code>description</code>
115 * property of the target {@link AbstractBean}.</p>
116 */
117 private boolean isDesigntime = false;
118
119 /***
120 * <p>Returns <code>true</code> if the current mode
121 * is design time.</p>
122 *
123 * @return <code>true</code> if design time
124 */
125 public boolean isDesigntime() {
126 return isDesigntime;
127 }
128
129 /***
130 * <p>Sets the design time to somthing other than
131 * the default <code>false</code> value.</p>
132 *
133 * @param isDesigntime load config descriptions
134 */
135 public void setDesigntime(boolean isDesigntime) {
136 this.isDesigntime = isDesigntime;
137 }
138
139
140 /***
141 * <p>Initialization method that is passed the <code>ServletContext</code>
142 * as a parameter. Loads the <code>sufixes</code> for the ServletContext
143 * initialization parameter.
144 * </p>
145 *
146 * @param context servlet context
147 */
148 public void init(ServletContext context) {
149 this.context = context;
150
151 if (suffixes == null) {
152 suffixes = new String[2];
153
154 suffixes[0] = context.getInitParameter(
155 Globals.CLAY_HTML_TEMPLATE_SUFFIX);
156 if (suffixes[0] == null) {
157 suffixes[0] = Globals.CLAY_DEFAULT_HTML_TEMPLATE_SUFFIX;
158 }
159
160 suffixes[1] = context.getInitParameter(
161 Globals.CLAY_XML_TEMPLATE_SUFFIX);
162 if (suffixes[1] == null) {
163 suffixes[1] = Globals.CLAY_DEFAULT_XML_TEMPLATE_SUFFIX;
164 }
165
166 }
167
168 String autoReloadClayFiles = context.getInitParameter(Globals.AUTO_RELOAD_CLAY_FILES);
169 if (autoReloadClayFiles != null) {
170 try {
171 isWatchDogOn = Boolean.valueOf(autoReloadClayFiles).booleanValue();
172 } catch (RuntimeException e) {
173 isWatchDogOn = false;
174 }
175
176 }
177
178
179 loadConfigFiles();
180 }
181
182
183 /***
184 * <p>Loads the {@link org.apache.shale.clay.component.Clay} configration files
185 * into the <code>displayElements</code> Map. The files are defined by the
186 * <code>clay-template-suffix</code> initialization parameter in the web deployment
187 * descriptor. The default configuration file "META-INF/view-config.xml" is always
188 * loaded from the shale-clay java archive.</p>
189 */
190 protected void loadConfigFiles() {
191
192 parser = new ClayXmlParser();
193 parser.setConfig(this);
194
195
196 StringBuffer configFiles = new StringBuffer(
197 Globals.DEFAULT_CLAY_CONFIG_FILE);
198
199
200 String param = context.getInitParameter(Globals.CLAY_CONFIG_FILES);
201
202
203 if (param != null && param.trim().length() > 0) {
204 configFiles.append(", ").append(param);
205
206 log.warn(messages.getMessage("config.deprecated.param",
207 new Object[] {Globals.CLAY_CONFIG_FILES,
208 Globals.CLAY_COMMON_CONFIG_FILES}));
209 }
210
211
212 param = context.getInitParameter(Globals.CLAY_COMMON_CONFIG_FILES);
213 if (param != null && param.trim().length() > 0) {
214 configFiles.append(", ").append(param);
215 }
216
217
218 parser.setConfig(this);
219
220
221 watchDogs = new TreeMap();
222
223
224 WatchDog watchDog = new WatchDog(getConfigDefinitions(configFiles.toString()),
225 Globals.DEFAULT_COMPONENT_CONFIG_WATCHDOG);
226
227
228 watchDogs.put(watchDog.getName(), watchDog);
229
230
231 watchDog.refresh(true);
232
233 param = null;
234 configFiles = null;
235
236 }
237
238 /***
239 * <p>Passed a comma delimited list of configuration files, this method returns
240 * an array of {@link ConfigBean.ConfigDefinition} defining the files.</p>
241 *
242 * @param configFiles comma seperated list of config files
243 * @return config definitions for the files
244 */
245 protected ConfigBean.ConfigDefinition[] getConfigDefinitions(String configFiles) {
246
247 List urls = new ArrayList();
248
249 ClassLoader classloader = Thread.currentThread().getContextClassLoader();
250 if (classloader == null) {
251 classloader = this.getClass().getClassLoader();
252 }
253
254
255 StringTokenizer tokenizer = new StringTokenizer(configFiles, ", ");
256 while (tokenizer.hasMoreTokens()) {
257 StringBuffer configFile = new StringBuffer(tokenizer.nextToken().trim());
258
259
260 int i = configFile.indexOf(Globals.CLASSPATH_PREFIX);
261 if (i > -1) {
262 configFile.delete(0, i + Globals.CLASSPATH_PREFIX.length());
263 }
264
265 try {
266 if (i > -1) {
267 for (Enumeration ui = classloader.getResources(configFile.toString());
268 ui.hasMoreElements();) {
269 urls.add(ui.nextElement());
270 }
271 } else {
272 URL url = context.getResource(configFile.toString());
273 if (url == null) {
274 throw new PageNotFoundException(messages.getMessage("file.notfound",
275 new Object[] {configFile.toString()}), configFile.toString());
276 }
277 urls.add(url);
278 }
279 } catch (IOException e) {
280 log.error(e);
281 }
282
283 configFile = null;
284 }
285 tokenizer = null;
286 classloader = null;
287
288 ConfigBean.ConfigDefinition[] configDefs = new ConfigBean.ConfigDefinition[urls.size()];
289
290 for (int i = 0; i < urls.size(); i++) {
291 configDefs[i] = new XmlConfigDef((URL) urls.get(i));
292 }
293
294 return configDefs;
295 }
296
297 /***
298 * <p>Returns the web container ServletContext.</p>
299 *
300 * @return servlet context
301 */
302 public ServletContext getServletContext() {
303 return context;
304 }
305
306 /***
307 * <p>Collection holding all the top-level components defined in the XML
308 * config files.</p>
309 */
310 protected Map displayElements = null;
311
312 /***
313 * <p>Constructor initializes the <code>displayElements</code>
314 * collection.</p>
315 */
316 public ComponentConfigBean() {
317 final int size = 1000;
318 displayElements = new HashMap(size);
319 }
320
321 /***
322 * <p>Factory method that returns a top-level {link ComponentBean} with a
323 * matching <code>jsfid</code> or <code>null</code> if not found.
324 * </p>
325 *
326 * @param jsfid id of component definition
327 * @return component definition for the jsfid
328 */
329 public ComponentBean getElement(String jsfid) {
330 ComponentBean element = null;
331 synchronized (displayElements) {
332 element = (ComponentBean) displayElements.get(jsfid);
333 }
334 return element;
335 }
336
337 /***
338 * <p>Adds a {link ComponentBean} to the <code>displayElement</code> map collection using
339 * the <code>jsfid</code> as the key.</p>
340 *
341 * @param obj component bean added to the map of elements
342 */
343 public void addChild(ComponentBean obj) {
344 if (obj.getJsfid() != null) {
345 displayElements.put(obj.getJsfid(), obj);
346 } else {
347 log.error(messages.getMessage("missing.jsfid.error",
348 new Object[] {"ComponentBean.jsfid", "ComponentConfigBean"}));
349 }
350
351 }
352
353 /***
354 * <p>This method is called on startup to resolve the meta inheritance relationships for
355 * each top-level components in the <code>displayElements</code> collection. There are
356 * three steps, find parents, check for circular relationships, and realize the relationships.
357 * </p>
358 */
359 public void resolveInheritance() {
360
361 if (log.isInfoEnabled()) {
362 log.info(messages.getMessage("resolve.inheritance.begin"));
363 }
364
365
366 if (log.isInfoEnabled()) {
367 log.info(messages.getMessage("finding.parents"));
368 }
369
370 Iterator di = displayElements.entrySet().iterator();
371 while (di.hasNext()) {
372 Map.Entry e = (Map.Entry) di.next();
373 ComponentBean b = (ComponentBean) e.getValue();
374
375 try {
376 assignParent(b);
377 } catch (RuntimeException e1) {
378 log.error(messages.getMessage("finding.parents.exception"), e1);
379 throw e1;
380 }
381
382 b = null;
383 e = null;
384 }
385 di = null;
386
387 if (log.isInfoEnabled()) {
388 log.info(messages.getMessage("checking.inheritance"));
389 }
390
391
392 di = displayElements.entrySet().iterator();
393 while (di.hasNext()) {
394 Map.Entry e = (Map.Entry) di.next();
395 ComponentBean b = (ComponentBean) e.getValue();
396
397 try {
398 checkCircularInheritance(b);
399 } catch (RuntimeException e1) {
400 log.error(
401 messages.getMessage("checking.inheritance.exception"),
402 e1);
403 throw e1;
404 }
405
406 b = null;
407 e = null;
408 }
409 di = null;
410
411 if (log.isInfoEnabled()) {
412 log.info(messages.getMessage("realizing.inheritance"));
413 }
414
415
416 di = displayElements.entrySet().iterator();
417 while (di.hasNext()) {
418 Map.Entry e = (Map.Entry) di.next();
419 ComponentBean b = (ComponentBean) e.getValue();
420
421 try {
422 realizingInheritance(b);
423 } catch (RuntimeException e1) {
424 log.error(messages.getMessage("realizing.inheritance.exception"), e1);
425 throw e1;
426 }
427
428
429
430 checkTree(b);
431
432 b = null;
433 e = null;
434 }
435 di = null;
436
437 if (log.isInfoEnabled()) {
438 log.info(messages.getMessage("resolve.inheritance.end"));
439 }
440
441 }
442
443 /***
444 * <p>Returns the root metadata component that is used to add to the component
445 * tree. This method might be overridden to broaden the scope to search for
446 * components outside of the <code>displayElement</code> cache.</p>
447 *
448 * @param jsfid id of a component bean
449 * @return component bean
450 */
451 protected ComponentBean getTopLevelElement(String jsfid) {
452
453
454 ComponentBean b = (ComponentBean) displayElements.get(jsfid);
455 if (b == null) {
456 throw new NullPointerException(messages.getMessage(
457 "jsfid.notfound", new Object[] { jsfid }));
458 }
459
460 return b;
461 }
462
463
464 /***
465 * <p>Called to assign the IsA parent to the {@link ComponentBean}
466 * using the <code>extends</code> attribute.
467 * </p>
468 *
469 * @param b component bean needing isa parent assigned
470 */
471 public void assignParent(ComponentBean b) {
472
473 synchronized (displayElements) {
474
475 if (b instanceof InnerComponentBean) {
476
477
478
479
480 if (b.getJsfid() != null) {
481 b.setExtends(b.getJsfid());
482 }
483 }
484
485
486 if (b.getExtends() != null) {
487
488
489 b.setIsAParent(getTopLevelElement(b.getExtends()));
490 }
491
492
493 Iterator ci = b.getChildrenIterator();
494 while (ci.hasNext()) {
495 assignParent((ComponentBean) ci.next());
496 }
497
498
499 if (b.getConverter() != null) {
500 assignParent(b.getConverter());
501 }
502
503
504 Iterator vi = b.getValidatorIterator();
505 while (vi.hasNext()) {
506 assignParent((ComponentBean) vi.next());
507 }
508
509
510 vi = b.getValueChangeListenerIterator();
511 while (vi.hasNext()) {
512 assignParent((ComponentBean) vi.next());
513 }
514 vi = null;
515
516
517 vi = b.getActionListenerIterator();
518 while (vi.hasNext()) {
519 assignParent((ComponentBean) vi.next());
520 }
521 vi = null;
522
523 }
524
525 }
526
527 /***
528 * <p>This overload handles fixing up {@link AttributeBean}
529 * inheritance.</p>
530 *
531 * @param a attribute needing inheritance resolved
532 */
533 protected void realizingInheritance(AttributeBean a) {
534
535
536
537 if (a.isInheritanceFinal()) {
538 return;
539 }
540
541
542 if (a.getIsAParent() == null) {
543 return;
544 }
545
546
547 realizingInheritance(a.getIsAParent());
548
549
550 if (a.getValue() == null) {
551 a.setValue(a.getIsAParent().getValue());
552 }
553
554
555 if (a.getBindingType() == null) {
556 a.setBindingType(a.getIsAParent().getBindingType());
557 }
558
559 if (a.getDescription() == null) {
560 a.setDescription(a.getIsAParent().getDescription());
561 }
562
563
564 a.setInheritanceFinal(true);
565
566 }
567
568 /***
569 * <p>This method is passed a {@link ComponentBean} and is
570 * recursively called for each contained component. It fixes up
571 * the meta inheritance relationships.
572 * </p>
573 *
574 * @param b component bean needing inheritance realized
575 */
576 public void realizingInheritance(ComponentBean b) {
577
578
579
580 if (b.isInheritanceFinal()) {
581 return;
582 }
583
584
585 if (b.getIsAParent() != null) {
586
587
588
589 realizingInheritance(b.getIsAParent());
590
591
592 if (b.getComponentType() == null) {
593 b.setComponentType(b.getIsAParent().getComponentType());
594 }
595
596 if (b.getAllowBody() == null) {
597 b.setAllowBody(b.getIsAParent().getAllowBody());
598 }
599
600 if (b.getFacetName() == null) {
601 b.setFacetName(b.getIsAParent().getFacetName());
602 }
603
604 if (b.getDescription() == null) {
605 b.setDescription(b.getIsAParent().getDescription());
606 }
607
608
609 Iterator pi = b.getIsAParent().getAttributeIterator();
610 while (pi.hasNext()) {
611 AttributeBean a = (AttributeBean) pi.next();
612 if (a != null) {
613
614
615 if ((a.getName() != null)
616 && (!b.getAttributes().containsKey(a.getName()))) {
617 b.getAttributes().put(a.getName(), a);
618 } else if (
619 (a.getName() != null)
620 && (b.getAttributes().containsKey(a.getName()))) {
621
622
623 AttributeBean ca =
624 (AttributeBean) b.getAttributes().get(a.getName());
625 ca.setIsAParent(a);
626 realizingInheritance(ca);
627 }
628
629 }
630 a = null;
631 }
632 pi = null;
633
634
635 pi = b.getIsAParent().getSymbols().entrySet().iterator();
636 while (pi.hasNext()) {
637 Map.Entry e = (Map.Entry) pi.next();
638 if (!b.getSymbols().containsKey(e.getKey())) {
639 b.getSymbols().put(e.getKey(), e.getValue());
640 }
641 }
642
643
644
645 TreeSet tmp = new TreeSet();
646
647 Iterator ci = b.getIsAParent().getChildrenIterator();
648 while (ci.hasNext()) {
649 ComponentBean c = (ComponentBean) ci.next();
650
651
652 if (c.getComponentType() == null) {
653 throw new NullPointerException(messages.getMessage("missing.componentType.exception",
654 new Object[] {c}));
655 }
656
657
658
659
660 if (!b.getChildren().contains(c)) {
661 tmp.add(c);
662 }
663
664 c = null;
665 }
666
667
668
669 b.setChildren(tmp);
670 tmp = null;
671 ci = null;
672
673 }
674
675
676 if (b.getComponentType() == null) {
677 throw new NullPointerException(messages.getMessage("missing.componentType.exception",
678 new Object[] {b}));
679 }
680
681
682
683 Iterator ci = b.getChildren().iterator();
684 while (ci.hasNext()) {
685 ComponentBean c = (ComponentBean) ci.next();
686 realizingInheritance(c);
687 if (c.getComponentType() == null) {
688 throw new NullPointerException(messages.getMessage("missing.componentType.exception",
689 new Object[] {c}));
690 }
691 c = null;
692 }
693 ci = null;
694
695
696 if (b.getConverter() == null && b.getIsAParent() != null
697 && b.getIsAParent().getConverter() != null) {
698 b.addConverter((ConverterBean) b.getIsAParent().getConverter());
699 }
700
701
702 if (b.getConverter() != null) {
703 realizingInheritance(b.getConverter());
704 }
705
706
707 if (b.getIsAParent() != null) {
708 Iterator vi = b.getIsAParent().getValidatorIterator();
709 while (vi.hasNext()) {
710 ComponentBean c = (ComponentBean) vi.next();
711
712 if (!b.getValidators().contains(c)) {
713 b.addValidator((ValidatorBean) c);
714 }
715 c = null;
716 }
717 vi = null;
718 }
719
720
721 Iterator vi = b.getValidatorIterator();
722 while (vi.hasNext()) {
723 ComponentBean c = (ComponentBean) vi.next();
724 realizingInheritance(c);
725 c = null;
726 }
727 vi = null;
728
729
730 if (b.getIsAParent() != null) {
731 vi = b.getIsAParent().getValueChangeListenerIterator();
732 while (vi.hasNext()) {
733 ComponentBean c = (ComponentBean) vi.next();
734 if (!b.getValueChangeListeners().contains(c)) {
735 b.addValueChangeListener((ValueChangeListenerBean) c);
736 }
737 c = null;
738 }
739 vi = null;
740 }
741
742
743 vi = b.getValueChangeListenerIterator();
744 while (vi.hasNext()) {
745 ComponentBean c = (ComponentBean) vi.next();
746 realizingInheritance(c);
747 c = null;
748 }
749 vi = null;
750
751
752 if (b.getIsAParent() != null) {
753 vi = b.getIsAParent().getActionListenerIterator();
754 while (vi.hasNext()) {
755 ComponentBean c = (ComponentBean) vi.next();
756 if (!b.getActionListeners().contains(c)) {
757 b.addActionListener((ActionListenerBean) c);
758 }
759 c = null;
760 }
761 vi = null;
762 }
763
764
765 vi = b.getActionListenerIterator();
766 while (vi.hasNext()) {
767 ComponentBean c = (ComponentBean) vi.next();
768 realizingInheritance(c);
769 c = null;
770 ci = null;
771 }
772 vi = null;
773
774
775 b.setInheritanceFinal(true);
776 }
777
778 /***
779 * <p>Walks up the isA parent chain looking for circular
780 * relationships. It returns a Stack of {@link ComponentBean}
781 * documenting the heritage. A runtime exception is thrown if
782 * a circular relationship is found.
783 * </p>
784 *
785 * @param b component bean having inheritance checked
786 * @return inheritance stack
787 */
788 protected Stack getGeneralizations(ComponentBean b) {
789 Stack heritage = new Stack();
790 if (!(b instanceof InnerComponentBean)) {
791 heritage.push(b);
792 }
793
794 ComponentBean node = b.getIsAParent();
795 while (node != null) {
796 if (!heritage.contains(node)) {
797 heritage.push(node);
798 } else {
799
800 heritage.push(node);
801 throw new RuntimeException(messages.getMessage("circular.inheritance.exception",
802 new Object[] {describeRelationships(heritage)}));
803 }
804 node = node.getIsAParent();
805 }
806
807 return heritage;
808 }
809
810 /***
811 * <p>Walks up the hasA parent chain looking for circular
812 * relationships. It returns a Stack of {@link ComponentBean}
813 * documenting the composition. A runtime exception is thrown if
814 * a circular relationship is found.
815 * </p>
816 *
817 * @param b component bean having composition checked
818 * @return stack of parents
819 */
820 protected Stack getAssociations(ComponentBean b) {
821 Stack relationships = new Stack();
822 ComponentBean node = b.getHasAParent();
823 while (node != null) {
824 if (!relationships.contains(node)) {
825 relationships.push(node);
826 } else {
827 relationships.push(node);
828
829 throw new RuntimeException(messages.getMessage("circular.composition.exception",
830 new Object[] {describeRelationships(relationships)}));
831 }
832 node = node.getIsAParent();
833 }
834 return relationships;
835 }
836
837 /***
838 * <p>Returns a StringBuffer with an xpath like expression of
839 * <code>jsfid</code> that describes the Stack of {@link ComponentBean}.
840 * </p>
841 *
842 * @param heritage stack of relationships to report on
843 * @return description of the stack
844 */
845 protected StringBuffer describeRelationships(Stack heritage) {
846 StringBuffer msg = new StringBuffer();
847 for (int i = 0; i < heritage.size(); i++) {
848 ComponentBean node = (ComponentBean) heritage.get(i);
849 if (i > 0) {
850 msg.insert(0, "/");
851 }
852 msg.insert(0, node.getJsfid());
853 }
854 return msg;
855 }
856
857 /***
858 * <p>Passed a {@link ComponentBean}, the method looks for several
859 * types of circular inheritances. It's recursively called for all
860 * contained components, children, validators, actionListeners,
861 * valueChangeListeners and Converter. A runtime exception is
862 * thrown if a invalid relationship is found.
863 * </p>
864 *
865 * @param b component bean to check
866 */
867 protected void checkCircularInheritance(ComponentBean b) {
868
869 Stack associations = getAssociations(b);
870 Stack generalizations = getGeneralizations(b);
871
872 if ((b.getHasAParent() != null)
873 && (b.getIsAParent() != null)
874 && (b.getHasAParent() == b.getIsAParent())) {
875
876 throw new RuntimeException(messages.getMessage("circular.child.parent.same.exception",
877 new Object[] {describeRelationships(generalizations), describeRelationships(associations) }));
878 }
879
880 if ((b.getHasAParent() != null)
881 && generalizations.contains(b.getHasAParent())) {
882
883 throw new RuntimeException(messages.getMessage("circular.child.extends.same.parent.exception",
884 new Object[] {describeRelationships(generalizations),
885 describeRelationships(getGeneralizations(b.getHasAParent()))}));
886 }
887
888 associations.clear();
889 generalizations.clear();
890
891 associations = null;
892 generalizations = null;
893
894 Iterator ci = b.getChildrenIterator();
895 while (ci.hasNext()) {
896 checkCircularInheritance((ComponentBean) ci.next());
897 }
898 ci = null;
899
900 if (b.getConverter() != null) {
901 checkCircularInheritance(b.getConverter());
902 }
903
904 Iterator vi = b.getValidatorIterator();
905 while (vi.hasNext()) {
906 checkCircularInheritance((ComponentBean) vi.next());
907 }
908
909 vi = b.getValueChangeListenerIterator();
910 while (vi.hasNext()) {
911 checkCircularInheritance((ComponentBean) vi.next());
912 }
913
914 vi = b.getActionListenerIterator();
915 while (vi.hasNext()) {
916 checkCircularInheritance((ComponentBean) vi.next());
917 }
918
919 vi = null;
920
921 }
922
923 /***
924 * <p>Recursively called to unassign isA and hasA parent
925 * relationships.
926 * </p>
927 *
928 * @param b component bean
929 */
930 protected void unassignParent(ComponentBean b) {
931
932
933 if (b.getIsAParent() != null) {
934 unassignParent(b.getIsAParent());
935 }
936 b.setHasAParent(null);
937
938 Iterator ai = b.getAttributeIterator();
939 while (ai.hasNext()) {
940 AttributeBean a = (AttributeBean) ai.next();
941 a.setHasAParent(null);
942 a.setIsAParent(null);
943 a = null;
944 }
945 ai = null;
946
947 Iterator ci = b.getChildrenIterator();
948 while (ci.hasNext()) {
949 unassignParent((ComponentBean) ci.next());
950 }
951 b.getChildren().clear();
952
953 if (b.getConverter() != null) {
954 unassignParent(b.getConverter());
955 }
956 b.addConverter(null);
957
958 Iterator vi = b.getValidatorIterator();
959 while (vi.hasNext()) {
960 unassignParent((ComponentBean) vi.next());
961 }
962 b.getValidators().clear();
963
964 vi = b.getValueChangeListenerIterator();
965 while (vi.hasNext()) {
966 unassignParent((ComponentBean) vi.next());
967 }
968 b.getValueChangeListeners().clear();
969
970 vi = b.getActionListenerIterator();
971 while (vi.hasNext()) {
972 unassignParent((ComponentBean) vi.next());
973 }
974 b.getValueChangeListeners().clear();
975
976 vi = null;
977
978 }
979
980
981 /***
982 * <p>Cleans up before a group of files are reloaded.</p>
983 *
984 * @param watchDogName group name for a group of config files or templates
985 */
986 protected void clear(String watchDogName) {
987 Iterator di = displayElements.entrySet().iterator();
988 while (di.hasNext()) {
989 Map.Entry e = (Map.Entry) di.next();
990 ComponentBean b = (ComponentBean) e.getValue();
991
992 try {
993 unassignParent(b);
994 } catch (RuntimeException e1) {
995
996 }
997
998 b = null;
999 e = null;
1000 }
1001 di = null;
1002
1003 displayElements.clear();
1004 }
1005
1006 /***
1007 * <p>The destroy method is invoked to clean up resources. By
1008 * dereferencing the complex graph of display elements
1009 * </p>
1010 */
1011 public void destroy() {
1012 clear(Globals.DEFAULT_COMPONENT_CONFIG_WATCHDOG);
1013 context = null;
1014
1015 if (parser != null) {
1016 parser.setConfig(null);
1017 }
1018 parser = null;
1019
1020 if (watchDogs != null) {
1021 Iterator wi = watchDogs.entrySet().iterator();
1022 while (wi.hasNext()) {
1023 Map.Entry e = (Map.Entry) wi.next();
1024 WatchDog watchDog = (WatchDog) e.getValue();
1025 if (watchDog != null) {
1026 watchDog.destroy();
1027 }
1028 }
1029 watchDogs.clear();
1030 watchDogs = null;
1031 }
1032
1033 }
1034
1035 /***
1036 * <p>Called by the {@link ConfigBeanFactory} to determine if this
1037 * instance of {@link ConfigBean} can handle finding the {@link ConfigBean}
1038 * from the <code>jsfid</code>.
1039 *
1040 * @param id jsfid
1041 * @return <code>true</code> if the jsfid can be handled
1042 */
1043 public boolean validMoniker(String id) {
1044
1045 for (int i = 0; i < suffixes.length; i++) {
1046 if (id.endsWith(suffixes[i])) {
1047 return false;
1048 }
1049 }
1050
1051 return true;
1052 }
1053
1054 /***
1055 * <p>Implementation of the Comparable interface. The <code>weight</code>
1056 * is used to determine the ordering of the registered {@link ConfigBean}
1057 * objects within the {@link ConfigBeanFactory}.
1058 * </p>
1059 *
1060 * @param config object to compare to
1061 * @return compares the weight of two config handlers
1062 */
1063 public int compareTo(Object config) {
1064
1065 ConfigBean compConfig = (ConfigBean) config;
1066 if (getWeight() > compConfig.getWeight()) {
1067 return 1;
1068 } else if (getWeight() > compConfig.getWeight()) {
1069 return -1;
1070 } else {
1071 return 0;
1072 }
1073
1074 }
1075 /***
1076 * <p>The weight is an attempt to make a plug-able system for
1077 * registering {@link ConfigBean} objects with the {@link ConfigBeanFactory}.
1078 * A custom implementation could be registered for a different composition
1079 * technique adding or overriding an existing implementation.
1080 * </p>
1081 *
1082 * @return <code>0</code>
1083 */
1084 public int getWeight() {
1085 return 0;
1086 }
1087
1088 /***
1089 * <p>This class defines a single configration file that is watched for
1090 * changes. In addition to the <code>URL</code> passed to the overloaded
1091 * constructor, the <code>lastModifed</code> date is kept as a state
1092 * variable.</p>
1093 */
1094 protected class XmlConfigDef implements ConfigBean.ConfigDefinition {
1095 /***
1096 * <p>The location of the config file.</p>
1097 */
1098 private URL configUrl = null;
1099
1100 /***
1101 * <p>Date the last time the file was modified as a <code>long</code>.</p>
1102 */
1103 private long lastModified = 0;
1104
1105 /***
1106 * <p>Overloaded constructor that requires the target config <code>URL</code>.
1107 *
1108 * @param configUrl file to load
1109 */
1110 public XmlConfigDef(URL configUrl) {
1111 this.configUrl = configUrl;
1112 }
1113
1114 /***
1115 * <p>Returns the target configuration file url.</p>
1116 *
1117 * @return file to load
1118 */
1119 public URL getConfigUrl() {
1120 return configUrl;
1121 }
1122
1123 /***
1124 * <p>Returns the last time the target configuration file was modified.</p>
1125 *
1126 * @return last modified timestamp of the config file
1127 */
1128 public long getLastModified() {
1129 return lastModified;
1130 }
1131
1132 /***
1133 * <p>Sets the last time the target configuration file was modified.</p>
1134 *
1135 * @param lastModified last time the file was changed
1136 */
1137 public void setLastModified(long lastModified) {
1138 this.lastModified = lastModified;
1139 }
1140 }
1141
1142 /***
1143 * <p>This inner class watches for changes in a array of {@link ConfigBean.ConfigDefinition}'s.
1144 * This collection defines the configuration files that the {@link org.apache.shale.clay.component.Clay}
1145 * component uses.</p>
1146 */
1147 protected class WatchDog {
1148
1149 /***
1150 * <p>Name assigned to the resource watcher.</p>
1151 */
1152 private String name = null;
1153
1154 /***
1155 * <p>Returns the name of the resource watcher.</p>
1156 *
1157 * @return the watched resource
1158 */
1159 public String getName() {
1160 return name;
1161 }
1162
1163 /***
1164 * <p>Array of config file definitions.</p>
1165 */
1166 private ConfigBean.ConfigDefinition[] configDefs = null;
1167
1168 /***
1169 * <p>Array of connections used to determine that the file has changed.</p>
1170 */
1171 private URLConnection[] connections = null;
1172
1173 /***
1174 * <p>Overloaded constructor that is passed the configuration file
1175 * definitions as a parameter. The name associated with the resource
1176 * watcher is also passed as a parameter.</p>
1177 *
1178 * @param configDefs files in the watch group
1179 * @param name the watch group name
1180 */
1181 public WatchDog(ConfigBean.ConfigDefinition[] configDefs, String name) {
1182 this.configDefs = configDefs;
1183 this.name = name;
1184 }
1185
1186 /***
1187 * <p>This method is invoked to dereference the private
1188 * array of config file definitions.</p>
1189 */
1190 public void destroy() {
1191 close();
1192 for (int i = 0; i < configDefs.length; i++) {
1193 configDefs[i] = null;
1194 }
1195
1196 configDefs = null;
1197 }
1198
1199 /***
1200 * <p>Loads an array of <code>URLConnection</code> corresponding to the
1201 * <code>configDefs</code>'s.</p>
1202 */
1203 private void open() {
1204
1205 if (connections != null) {
1206 close();
1207 }
1208
1209 connections = new URLConnection[configDefs.length];
1210 for (int i = 0; i < configDefs.length; i++) {
1211
1212 try {
1213 connections[i] = configDefs[i].getConfigUrl()
1214 .openConnection();
1215 } catch (IOException e) {
1216 log.error(messages.getMessage("parser.load.error",
1217 new Object[] { configDefs[i].getConfigUrl()
1218 .getPath() }), e);
1219 }
1220 }
1221 }
1222
1223 /***
1224 * <p>Performs some extra cleanup on the open array of
1225 * <code>connections</code>.</p>
1226 */
1227 private void close() {
1228 if (connections == null) {
1229 return;
1230 }
1231
1232 for (int i = 0; i < connections.length; i++) {
1233 connections[i] = null;
1234 }
1235
1236 connections = null;
1237 }
1238
1239 /***
1240 * <p>Iterates over the open <code>connections</code> looking
1241 * for files that have changed. A <code>true</code> value is
1242 * returned if one of the <code>configDefs</code> has been
1243 * modified since last loaded.</p>
1244 *
1245 * @return <code>true</code> if the file has been modified
1246 */
1247 private boolean isDirty() {
1248 for (int i = 0; i < configDefs.length; i++) {
1249 if (configDefs[i].getLastModified() < connections[i]
1250 .getLastModified()) {
1251 return true;
1252 }
1253 }
1254 return false;
1255 }
1256
1257 /***
1258 * <p>This method is the watch dog timmer. It's invoked to determine
1259 * if any of the files have changed since the last time they were loaded.
1260 * If a change has occured on any of the <code>configDefs</code> or
1261 * the <code>forceReload</code> param is <code>true</code>, all the
1262 * files are reloaded and the last modified date is reset in the
1263 * {@link ConfigBean.ConfigDefinition}. A <code>true</code> value is
1264 * returned if the files were refreshed.
1265 * </p>
1266 *
1267 * @param forceReload reload the group of config files
1268 * @return <code>true</code> if the group was reloaded
1269 */
1270 public boolean refresh(boolean forceReload) {
1271
1272 boolean wasDirty = false;
1273
1274 int i = 0;
1275 try {
1276 open();
1277 if (forceReload || isDirty()) {
1278 wasDirty = true;
1279 clear(getName());
1280 for (i = 0; i < configDefs.length; i++) {
1281
1282 if (log.isInfoEnabled()) {
1283 log.info(messages.getMessage("parser.load.file",
1284 new Object[] { configDefs[i].getConfigUrl()
1285 .getPath() }));
1286 }
1287
1288 try {
1289
1290 configDefs[i].setLastModified(connections[i]
1291 .getLastModified());
1292 parser.loadConfigFile(connections[i].getURL(), getName());
1293
1294 } catch (IOException e) {
1295 log.error(messages.getMessage("parser.load.error",
1296 new Object[] { configDefs[i].getConfigUrl()
1297 .getPath() }), e);
1298 } catch (SAXException e) {
1299 log.error(messages.getMessage("parser.load.error",
1300 new Object[] { configDefs[i].getConfigUrl()
1301 .getPath() }), e);
1302 }
1303 }
1304
1305 resolveInheritance();
1306 }
1307 } finally {
1308 close();
1309 }
1310
1311 return wasDirty;
1312 }
1313
1314 }
1315
1316 /***
1317 * <p>This method should be called from key points in the application to invoke
1318 * automatic reloading of the configuration files if they have been modified since
1319 * last reloaded. If the <code>forceReload</code> flag is <code>true</code> the files are
1320 * reloaded. A <code>true</code> return value indicates the config files
1321 * were reloaded.</p>
1322 *
1323 * @param forceReload reload the files
1324 * @return files were reloaded
1325 */
1326 public boolean refresh(boolean forceReload) {
1327
1328 boolean wasDirty = false;
1329
1330 WatchDog watchDog = (WatchDog) watchDogs.get(Globals.DEFAULT_COMPONENT_CONFIG_WATCHDOG);
1331
1332
1333 if (!ComponentConfigBean.this.isWatchDogOn || watchDog == null) {
1334 return wasDirty;
1335 }
1336
1337 synchronized (displayElements) {
1338 wasDirty = watchDog.refresh(forceReload);
1339 }
1340
1341 watchDog = null;
1342
1343 return wasDirty;
1344 }
1345
1346
1347 /***
1348 * <p>A static string array of faces component types that are naming
1349 * containers.</p>
1350 */
1351 protected static final String[] NAMING_CONTAINER_TYPES = {
1352 "javax.faces.HtmlForm",
1353 "javax.faces.HtmlDataTable",
1354 "org.apache.shale.view.Subview",
1355 "javax.faces.NamingContainer"};
1356
1357 /***
1358 * <p>Checks the <code>componentType</code> against the <code>NAMING_CONTAINER_TYPES</code>
1359 * list to determine if it is a naming container. Component id's must be unique within a
1360 * naming container. Returns a <code>true</code> value if the <code>componentType</code>
1361 * is a naming container; otherwise, returns <code>false</code>.</p>
1362 *
1363 * @param componentType type of the component
1364 * @return <code>true</code> if the component type is a naming comtainer
1365 */
1366 protected boolean isNamingContainer(String componentType) {
1367 boolean flag = false;
1368 for (int i = 0; i < NAMING_CONTAINER_TYPES.length; i++) {
1369 if (NAMING_CONTAINER_TYPES[i].equals(componentType)) {
1370 flag = true;
1371 break;
1372 }
1373 }
1374
1375 return flag;
1376 }
1377
1378
1379 /***
1380 * <p>Recursively walks the tree of component metadata verifying
1381 * there is not a duplicate component id within a naming container.
1382 * A root {@link ComponentBean} is passed as a single parameter.
1383 * The overloaded <code>checkTree(List, ComponentBean)</code> is
1384 * invoked to process components under a naming container.</p>
1385 *
1386 * @param b root of the component tree
1387 */
1388 public void checkTree(ComponentBean b) {
1389 if (log.isDebugEnabled()) {
1390 log.debug(messages.getMessage("check.tree", new Object[] {b.getComponentType()}));
1391 }
1392
1393 List componentIds = new ArrayList();
1394 checkTree(componentIds, b);
1395 componentIds.clear();
1396 componentIds = null;
1397 }
1398
1399
1400
1401 /***
1402 * <p>Verifies there is not a duplicate component id within a naming container.
1403 * A list of accumulating <code>componentIds</code> and a
1404 * root {@link ComponentBean} is passed as parameters. A runtime
1405 * exception is thrown if a duplicate id is encountered.</p>
1406 *
1407 * @param componentIds list of component id's in the naming container
1408 * @param b parent component bean
1409 */
1410 protected void checkTree(List componentIds, ComponentBean b) {
1411
1412
1413 String id = b.getId();
1414 if (id != null && (id.indexOf('@') == -1)) {
1415 if (componentIds.contains(id)) {
1416 throw new NullPointerException(messages.getMessage("duplicate.componentid.exception",
1417 new Object[] {id, b}));
1418 } else {
1419 componentIds.add(id);
1420 }
1421 }
1422
1423 Iterator ci = b.getChildrenIterator();
1424 while (ci.hasNext()) {
1425 ComponentBean c = (ComponentBean) ci.next();
1426 if (isNamingContainer(c.getComponentType())) {
1427 checkTree(c);
1428 } else {
1429 checkTree(componentIds, c);
1430 }
1431 }
1432
1433 }
1434
1435 }