2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to you under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * $Id: ComponentConfigBean.java 467369 2006-10-24 16:04:31Z gvanmatre $
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         //loads the config files
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         // grab the default config file
196         StringBuffer configFiles = new StringBuffer(
197                 Globals.DEFAULT_CLAY_CONFIG_FILE);
198 
199         // a comma delimited value list of config files
200         String param = context.getInitParameter(Globals.CLAY_CONFIG_FILES);
201 
202         // add the default config file
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         // a comma delimited value list of config files
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         // pass the config bean to the parser
218         parser.setConfig(this);
219 
220         // map holding the resource watchers
221         watchDogs = new TreeMap();
222 
223         // create the watch dog with a list of config files to look for changes
224         WatchDog watchDog = new WatchDog(getConfigDefinitions(configFiles.toString()),
225                 Globals.DEFAULT_COMPONENT_CONFIG_WATCHDOG);
226 
227         // adds the watcher to a map identified by name
228         watchDogs.put(watchDog.getName(), watchDog);
229 
230         // loads the config files
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         // convert a tokenized list of configuration files into an array of urls
255         StringTokenizer tokenizer = new StringTokenizer(configFiles, ", ");
256         while (tokenizer.hasMoreTokens()) {
257             StringBuffer configFile = new StringBuffer(tokenizer.nextToken().trim());
258 
259             //look for a classpath prefix.
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         // fixup heritage links
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         // Check for circular inheritance
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         // now realizing inheritance
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             //check to make sure that there is not a duplicate component id
429             //within the same naming container.
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         // find the top-level display element associated with the subtree
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                 // these elements are like inner classes
478                 // set the extends to the element so the
479                 // parent can be generically resolved
480                 if (b.getJsfid() != null) {
481                     b.setExtends(b.getJsfid());
482                 }
483             }
484 
485             // look for a meta inheritance property
486             if (b.getExtends() != null) {
487 
488                 // assign the parent to a top-level display element
489                 b.setIsAParent(getTopLevelElement(b.getExtends()));
490             }
491 
492             // resolve inheritance of nested components
493             Iterator ci = b.getChildrenIterator();
494             while (ci.hasNext()) {
495                 assignParent((ComponentBean) ci.next());
496             }
497 
498             // resolve inheritance of converter
499             if (b.getConverter() != null) {
500                 assignParent(b.getConverter());
501             }
502 
503             // resolve inheritance of validators
504             Iterator vi = b.getValidatorIterator();
505             while (vi.hasNext()) {
506                 assignParent((ComponentBean) vi.next());
507             }
508 
509             // resolve inheritance of value change listeners
510             vi = b.getValueChangeListenerIterator();
511             while (vi.hasNext()) {
512                 assignParent((ComponentBean) vi.next());
513             }
514             vi = null;
515 
516             // resolve inheritance of action listeners
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         // look to see if the inheritance has
536         // already been resolved
537         if (a.isInheritanceFinal()) {
538             return;
539         }
540 
541         // if no parent, nothing to do
542         if (a.getIsAParent() == null) {
543             return;
544         }
545 
546         // traverse up to the parent and work down
547         realizingInheritance(a.getIsAParent());
548 
549         // inherit attribute value
550         if (a.getValue() == null) {
551             a.setValue(a.getIsAParent().getValue());
552         }
553 
554         // inherit late binding type
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         // set final indicator
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         // look at the final indicator to determine
579         // if inheritance has already been resolved
580         if (b.isInheritanceFinal()) {
581             return;
582         }
583 
584         // does the node have a parent
585         if (b.getIsAParent() != null) {
586 
587             // resolve the parents inheritance and
588             // work down
589             realizingInheritance(b.getIsAParent());
590 
591             // inherit component type
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             // inherit parents attributes
609             Iterator pi = b.getIsAParent().getAttributeIterator();
610             while (pi.hasNext()) {
611                 AttributeBean a = (AttributeBean) pi.next();
612                 if (a != null) {
613                     // if the parent has and attribute the child doesn't
614                     // then the child inherits it
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                         // if the parent has an attribute, let the child
622                         // attribute inherit properties of the attribute
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             //inherit symbols
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             //inherit elements from the parent.  elements are identified/ordered by
644             //the renderid in the set.
645             TreeSet tmp = new TreeSet();
646             // get the parents children/elements
647             Iterator ci = b.getIsAParent().getChildrenIterator();
648             while (ci.hasNext()) {
649                 ComponentBean c = (ComponentBean) ci.next();
650                 // the parent of a element can only be a display element
651                 // and a display element must have or inherit a component type
652                 if (c.getComponentType() == null) {
653                     throw new NullPointerException(messages.getMessage("missing.componentType.exception",
654                             new Object[] {c}));
655                 }
656 
657                 // if the child node doesn't contain the parent's
658                 // element identified by renderId then add it to a
659                 // temp set
660                 if (!b.getChildren().contains(c)) {
661                     tmp.add(c);
662                 }
663 
664                 c = null;
665             }
666 
667             // merge the delta of parent elements not found in the
668             // child's element set into the child's element set
669             b.setChildren(tmp);
670             tmp = null;
671             ci = null;
672 
673         }
674 
675         // at this point, the component id should be defined or inherited
676         if (b.getComponentType() == null) {
677             throw new NullPointerException(messages.getMessage("missing.componentType.exception",
678                     new Object[] {b}));
679         }
680 
681         // iterate thru the inherited set of elements resolving
682         // their inheritance
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         // inherit converter from parent
696         if (b.getConverter() == null && b.getIsAParent() != null
697                 && b.getIsAParent().getConverter() != null) {
698             b.addConverter((ConverterBean) b.getIsAParent().getConverter());
699         }
700 
701         // resolve the inheritance of a nested converter
702         if (b.getConverter() != null) {
703             realizingInheritance(b.getConverter());
704         }
705 
706         // inheritance of all parent validators
707         if (b.getIsAParent() != null) {
708            Iterator vi = b.getIsAParent().getValidatorIterator();
709            while (vi.hasNext()) {
710                ComponentBean c = (ComponentBean) vi.next();
711                // check to make sure the child doesn't have one
712                if (!b.getValidators().contains(c)) {
713                   b.addValidator((ValidatorBean) c);
714                }
715                c = null;
716            }
717            vi = null;
718         }
719 
720         // resovle inheritance of all nested validators
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         // inheritance of all value change listeners
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         // resolve inheritance of all nested value change listeners
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         // inheritance of all action listeners
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         // resolve inheritance of all nested action listeners
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         // toggle on the final flag
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                 // construct a error message from the heritage stack
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         // play nicely and clean up your mess
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                 // log.error(e1);
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         // is auto watch turned off
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           //check fo duplicate component id's
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 }