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