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 518103 2007-03-14 11:43:53Z hermod $
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         //loads the config files
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         // grab the default config file
198         StringBuffer configFiles = new StringBuffer(
199                 Globals.DEFAULT_CLAY_CONFIG_FILE);
200 
201         // a comma delimited value list of config files
202         String param = context.getInitParameter(Globals.CLAY_CONFIG_FILES);
203 
204         // add the default config file
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         // a comma delimited value list of config files
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         // pass the config bean to the parser
220         parser.setConfig(this);
221 
222         // map holding the resource watchers
223         watchDogs = Collections.synchronizedMap(new TreeMap());
224 
225         // create the watch dog with a list of config files to look for changes
226         WatchDog watchDog = new WatchDog(getConfigDefinitions(configFiles.toString()),
227                 Globals.DEFAULT_COMPONENT_CONFIG_WATCHDOG);
228 
229         // adds the watcher to a map identified by name
230         watchDogs.put(watchDog.getName(), watchDog);
231 
232         // loads the config files
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         // convert a tokenized list of configuration files into an array of urls
257         StringTokenizer tokenizer = new StringTokenizer(configFiles, ", ");
258         while (tokenizer.hasMoreTokens()) {
259             StringBuffer configFile = new StringBuffer(tokenizer.nextToken().trim());
260 
261             //look for a classpath prefix.
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         // fixup heritage links
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         // Check for circular inheritance
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         // now realizing inheritance
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             //check to make sure that there is not a duplicate component id
432             //within the same naming container.
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         // find the top-level display element associated with the subtree
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             // these elements are like inner classes
480             // set the extends to the element so the
481             // parent can be generically resolved
482             if (b.getJsfid() != null) {
483                 b.setExtends(b.getJsfid());
484             }
485         }
486 
487         // look for a meta inheritance property
488         if (b.getExtends() != null) {
489 
490             // assign the parent to a top-level display element
491             b.setIsAParent(getTopLevelElement(b.getExtends()));
492         }
493 
494         // resolve inheritance of nested components
495         Iterator ci = b.getChildrenIterator();
496         while (ci.hasNext()) {
497             assignParent((ComponentBean) ci.next());
498         }
499 
500         // resolve inheritance of converter
501         if (b.getConverter() != null) {
502             assignParent(b.getConverter());
503         }
504 
505         // resolve inheritance of validators
506         Iterator vi = b.getValidatorIterator();
507         while (vi.hasNext()) {
508             assignParent((ComponentBean) vi.next());
509         }
510 
511         // resolve inheritance of value change listeners
512         vi = b.getValueChangeListenerIterator();
513         while (vi.hasNext()) {
514             assignParent((ComponentBean) vi.next());
515         }
516         vi = null;
517 
518         // resolve inheritance of action listeners
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         // look to see if the inheritance has
537         // already been resolved
538         if (a.isInheritanceFinal()) {
539             return;
540         }
541 
542         // if no parent, nothing to do
543         if (a.getIsAParent() == null) {
544             return;
545         }
546 
547         // traverse up to the parent and work down
548         realizingInheritance(a.getIsAParent());
549 
550         // inherit attribute value
551         if (a.getValue() == null) {
552             a.setValue(a.getIsAParent().getValue());
553         }
554 
555         // inherit late binding type
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         // set final indicator
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         // look at the final indicator to determine
580         // if inheritance has already been resolved
581         if (b.isInheritanceFinal()) {
582             return;
583         }
584 
585         // does the node have a parent
586         if (b.getIsAParent() != null) {
587 
588             // resolve the parents inheritance and
589             // work down
590             realizingInheritance(b.getIsAParent());
591 
592             // inherit component type
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             // inherit parents attributes
610             Iterator pi = b.getIsAParent().getAttributeIterator();
611             while (pi.hasNext()) {
612                 AttributeBean a = (AttributeBean) pi.next();
613                 if (a != null) {
614                     // if the parent has and attribute the child doesn't
615                     // then the child inherits it
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                         // if the parent has an attribute, let the child
623                         // attribute inherit properties of the attribute
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             //inherit symbols
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             //inherit elements from the parent.  elements are identified/ordered by
645             //the renderid in the set.
646             TreeSet tmp = new TreeSet();
647             // get the parents children/elements
648             Iterator ci = b.getIsAParent().getChildrenIterator();
649             while (ci.hasNext()) {
650                 ComponentBean c = (ComponentBean) ci.next();
651                 // the parent of a element can only be a display element
652                 // and a display element must have or inherit a component type
653                 if (c.getComponentType() == null) {
654                     throw new NullPointerException(messages.getMessage("missing.componentType.exception",
655                             new Object[] {c}));
656                 }
657 
658                 // if the child node doesn't contain the parent's
659                 // element identified by renderId then add it to a
660                 // temp set
661                 if (!b.getChildren().contains(c)) {
662                     tmp.add(c);
663                 }
664 
665                 c = null;
666             }
667 
668             // merge the delta of parent elements not found in the
669             // child's element set into the child's element set
670             b.setChildren(tmp);
671             tmp = null;
672             ci = null;
673 
674         }
675 
676         // at this point, the component id should be defined or inherited
677         if (b.getComponentType() == null) {
678             throw new NullPointerException(messages.getMessage("missing.componentType.exception",
679                     new Object[] {b}));
680         }
681 
682         // iterate thru the inherited set of elements resolving
683         // their inheritance
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         // inherit converter from parent
697         if (b.getConverter() == null && b.getIsAParent() != null
698                 && b.getIsAParent().getConverter() != null) {
699             b.addConverter((ConverterBean) b.getIsAParent().getConverter());
700         }
701 
702         // resolve the inheritance of a nested converter
703         if (b.getConverter() != null) {
704             realizingInheritance(b.getConverter());
705         }
706 
707         // inheritance of all parent validators
708         if (b.getIsAParent() != null) {
709            Iterator vi = b.getIsAParent().getValidatorIterator();
710            while (vi.hasNext()) {
711                ComponentBean c = (ComponentBean) vi.next();
712                // check to make sure the child doesn't have one
713                if (!b.getValidators().contains(c)) {
714                   b.addValidator((ValidatorBean) c);
715                }
716                c = null;
717            }
718            vi = null;
719         }
720 
721         // resovle inheritance of all nested validators
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         // inheritance of all value change listeners
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         // resolve inheritance of all nested value change listeners
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         // inheritance of all action listeners
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         // resolve inheritance of all nested action listeners
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         // toggle on the final flag
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                 // construct a error message from the heritage stack
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         // play nicely and clean up your mess
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                 // log.error(e1);
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         // is auto watch turned off
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           //check fo duplicate component id's
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 }