KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > sape > carbon > core > config > format > jdom > JDOMConfigurationProxy


1 /*
2  * The contents of this file are subject to the Sapient Public License
3  * Version 1.0 (the "License"); you may not use this file except in compliance
4  * with the License. You may obtain a copy of the License at
5  * http://carbon.sf.net/License.html.
6  *
7  * Software distributed under the License is distributed on an "AS IS" basis,
8  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9  * the specific language governing rights and limitations under the License.
10  *
11  * The Original Code is The Carbon Component Framework.
12  *
13  * The Initial Developer of the Original Code is Sapient Corporation
14  *
15  * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
16  */

17
18 package org.sape.carbon.core.config.format.jdom;
19
20
21 import java.beans.IndexedPropertyDescriptor JavaDoc;
22 import java.beans.IntrospectionException JavaDoc;
23 import java.beans.Introspector JavaDoc;
24 import java.beans.PropertyDescriptor JavaDoc;
25 import java.lang.reflect.Array JavaDoc;
26 import java.lang.reflect.Proxy JavaDoc;
27 import java.util.HashMap JavaDoc;
28 import java.util.Iterator JavaDoc;
29 import java.util.List JavaDoc;
30 import java.util.Map JavaDoc;
31
32 import org.sape.carbon.core.config.Config;
33 import org.sape.carbon.core.config.Configuration;
34 import org.sape.carbon.core.config.InvalidConfigurationException;
35 import org.sape.carbon.core.config.format.AbstractConfigurationProxy;
36 import org.sape.carbon.core.config.format.ConfigurationFormatException;
37 import org.sape.carbon.core.config.node.Node;
38 import org.sape.carbon.core.config.type.ConfigurationTypeException;
39 import org.sape.carbon.core.config.type.ConfigurationTypeService;
40 import org.sape.carbon.core.config.type.ConfigurationTypeServiceFactory;
41 import org.sape.carbon.core.exception.IllegalStateException;
42 import org.sape.carbon.core.exception.InvalidParameterException;
43 import org.sape.carbon.core.util.string.StringUtil;
44
45 import org.jdom.Document;
46 import org.jdom.Element;
47
48 /**
49  * <p>This is an extension of an <code>AbstractConfigurationProxy</code>
50  * that is backed by a JDOM Document data structure.
51  * </p>
52  *
53  * Copyright 2002 Sapient
54  * @since carbon 1.0
55  * @author Greg Hinkle, February 2002
56  * @author Douglas Voet
57  * @version $Revision: 1.52 $($Author: dvoet $ / $Date: 2003/09/24 14:09:55 $)
58  */

59 public class JDOMConfigurationProxy extends AbstractConfigurationProxy {
60     /** Holds the type service for configuration. */
61     protected final ConfigurationTypeService typeService =
62         ConfigurationTypeServiceFactory.getInstance();
63
64     /**
65      * Map for caching previously accessed attributes
66      * @since carbon 1.1
67      */

68     protected Map JavaDoc attributeCache = new HashMap JavaDoc();
69
70     /** Key attribute for a map element. */
71     public static final String JavaDoc MAP_KEY_ATTRIBUTE = "Key";
72
73     /**
74      * Constructs a JDOMConfigurationProxy for the specified
75      * class type. This class only supports subclasses of the
76      * {@link org.sape.carbon.core.config.Configuration} object.
77      * @param configurationClass The type of Configuration object that should be
78      * returned
79      */

80     public JDOMConfigurationProxy(Class JavaDoc configurationClass) {
81         super(configurationClass);
82     }
83
84     /**
85      * Constructs a JDOMConfigurationProxy for the specified
86      * class type. This class only supports subclasses of the
87      * {@link org.sape.carbon.core.config.Configuration} object.
88      * The supplied document is used as the backing store for
89      * this configuration object's data.
90      * @param document The JDOM Document object representing a configurations
91      * data
92      * @param configurationClass The type of configuration to be implemented
93      */

94     public JDOMConfigurationProxy(
95         Document document,
96         Class JavaDoc configurationClass) {
97
98         super(document, document.getRootElement(), configurationClass);
99     }
100
101     /**
102      * Constructs a JDOMConfigurationProxy for the specified
103      * class type. This class only supports subclasses of the
104      * {@link org.sape.carbon.core.config.Configuration} object.
105      * The supplied Element is used as the backing datastore
106      * for this configuration object and it's document object
107      * will be modified when this object is modified.
108      * @param element The JDOM element object representing to root node
109      * of this configuration object
110      * @param configurationClass The type of configuration object that will be
111      * implemented
112      */

113     public JDOMConfigurationProxy(Element element, Class JavaDoc configurationClass) {
114
115         super(null, element, configurationClass);
116     }
117
118     /**
119      * <P>This method overrides the base proxy's <code>toString</code>
120      * implementation so that useful data is printed out when toString is
121      * called.
122      * </P>
123      *
124      * @param proxy the proxy to print
125      * @return a string representation of this configuration object
126      */

127     public String JavaDoc proxyToString(Object JavaDoc proxy) {
128         return this.getDocumentType().getName()
129             + " ["
130             + this.getConfigurationName()
131             + "]";
132     }
133
134     /**
135      * <P>This method clones this Configuration object through a deep-copy of
136      * it's underlying datastructure. The resulting configuration object is
137      * a new object and can be altered without affecting the original.</P>
138      *
139      * @return Object the cloned configuration object
140      */

141     public Object JavaDoc clone() {
142
143         JDOMConfigurationProxy proxy = null;
144
145         if (this.document == null) {
146             // Expected for nested configurations
147
Element newElement = (Element) super.element.clone();
148
149             // Manually construct the invocation handler and rewrap it.
150
// This is done seperately because "newProxy()" would only use
151
// the root element and would loose document information such
152
// as the encoding type.
153
proxy =
154                 new JDOMConfigurationProxy(newElement, this.getDocumentType());
155
156         } else {
157             Document doc = (Document) this.document.clone();
158
159             // Manually construct the invocation handler and rewrap it.
160
// This is done seperately because "newProxy()" would only use
161
// the root element and would loose document information such
162
// as the encoding type.
163
proxy =
164                 new JDOMConfigurationProxy(doc, this.getDocumentType());
165         }
166
167         proxy.setConfigurationName(this.getConfigurationName());
168
169         Configuration config =
170             (Configuration) Proxy.newProxyInstance(
171                 this.getClass().getClassLoader(),
172                 new Class JavaDoc[] {this.getDocumentType()},
173                 proxy);
174
175         return config;
176     }
177
178     /**
179      * @inherit
180      * @see AbstractConfigurationProxy#lookupAttribute
181      */

182     public Object JavaDoc lookupAttribute(String JavaDoc attributeName, Class JavaDoc returnType) {
183         Object JavaDoc attributeValue = this.attributeCache.get(attributeName);
184         if (attributeValue == null) {
185
186             try {
187                 // lookup the element in the document
188
Element childElement =
189                     this.element.getChild(attributeName);
190
191                 // format and return the element
192
attributeValue =
193                     formatElement(childElement, attributeName, returnType);
194
195                 if(this.typeService.isCacheableType(returnType)) {
196                     this.attributeCache.put(attributeName, attributeValue);
197                 }
198
199             } catch (ConfigurationFormatException cfe) {
200                 throw new InvalidConfigurationException(
201                     this.getClass(),
202                     this.getConfigurationName(),
203                     attributeName,
204                     "Format error on configuration value.",
205                     cfe);
206             }
207         }
208
209         return attributeValue;
210     }
211
212
213     /**
214      * <P>Retrieves an array of objects of the specified type that have
215      * the specified name. This might get a list of dependent objects or
216      * a list of string values.</P>
217      *
218      * @param attributeName the name of the array to retrieve
219      * @param componentType the type of the objects that should be retrieved
220      * within the array
221      * @return an array of objects that match the name and type specified
222      */

223     public Object JavaDoc getArray(String JavaDoc attributeName, Class JavaDoc componentType) {
224
225         Object JavaDoc attributeArray = this.attributeCache.get(attributeName);
226         if (attributeArray == null) {
227             Element collectionElement =
228                 getCollectionElement(attributeName, Array JavaDoc.class);
229
230             List JavaDoc list = collectionElement.getChildren(attributeName);
231             int length = list.size();
232
233             attributeArray = Array.newInstance(componentType, length);
234
235             try {
236                 for (int i = 0; i < length; i++) {
237                     // get the next element
238
Element element = (Element) list.get(i);
239
240                     // format it
241
Object JavaDoc attribute =
242                         formatContainedElement(
243                             element,
244                             attributeName,
245                             String.valueOf(i),
246                             componentType);
247
248                     // add it to the array
249
Array.set(attributeArray, i, attribute);
250                 }
251                 if (this.typeService.isCacheableType(componentType)) {
252                     this.attributeCache.put(attributeName, attributeArray);
253                 }
254             } catch (ConfigurationFormatException cfe) {
255                 throw new InvalidConfigurationException(
256                     this.getClass(),
257                     this.getConfigurationName(),
258                     attributeName,
259                     "Format error on configuration value.",
260                     cfe);
261             }
262         }
263         return attributeArray;
264     }
265
266     /**
267      * @inherit
268      * @see AbstractConfigurationProxy#getMap
269      */

270     public Map JavaDoc getMap(String JavaDoc tagName, Class JavaDoc contentType) {
271
272         Map JavaDoc attributeMap = (Map JavaDoc) this.attributeCache.get(tagName);
273         if (attributeMap == null) {
274
275             Element collectionElement =
276                 getCollectionElement(tagName, Map JavaDoc.class);
277
278             List JavaDoc list = collectionElement.getChildren(tagName);
279             int length = list.size();
280
281
282             attributeMap = new HashMap JavaDoc();
283
284
285             try {
286                 for (int i = 0; i < length; i++) {
287                     // get the next element
288
Element element = (Element) list.get(i);
289                     String JavaDoc unformattedKey =
290                         element.getAttributeValue(MAP_KEY_ATTRIBUTE);
291
292                     if (unformattedKey == null) {
293                         throw new InvalidConfigurationException(
294                             this.getClass(),
295                             this.getConfigurationName(),
296                             tagName,
297                             "All map entries in configuration must "
298                             + "have a valid [Key] attribute. (And "
299                             + "its case sensitive)");
300                     }
301                     
302                     // format key as a String
303
// at this point, this is done only so that token
304
// replacement will be performed for keys, if in the
305
// future, we want to support key objects other than
306
// Strings, replace the hard coded String.class with the
307
// appropriate Class object and the type service will do
308
// the rest
309
String JavaDoc key = (String JavaDoc)
310                         this.typeService.toObject(String JavaDoc.class, unformattedKey);
311
312                     // format it
313
Object JavaDoc attribute = formatContainedElement(
314                             element, tagName, key, contentType);
315
316                     // add it to the map
317
if (this.typeService.isCacheableType(contentType)) {
318                         attributeMap.put(key, attribute);
319                     }
320                 }
321             } catch (NullPointerException JavaDoc npe) {
322                 throw new InvalidConfigurationException(
323                     this.getClass(),
324                     this.getConfigurationName(),
325                     tagName,
326                     "Map tags must have keys specified by the "
327                         + MAP_KEY_ATTRIBUTE + " attribute.",
328                     npe);
329             } catch (ConfigurationFormatException cfe) {
330
331                 throw new InvalidConfigurationException(
332                     this.getClass(),
333                     this.getConfigurationName(),
334                     tagName,
335                     "Format error on configuration value.",
336                     cfe);
337             } catch (ConfigurationTypeException cte) {
338
339                 throw new InvalidConfigurationException(
340                     this.getClass(),
341                     this.getConfigurationName(),
342                     tagName,
343                     "Format error on configuration key.",
344                     cte);
345             }
346             this.attributeCache.put(tagName, attributeMap);
347         }
348         return attributeMap;
349     }
350     /**
351      * @inherit
352      * @see AbstractConfigurationProxy#alterAttribute
353      */

354     public void alterAttribute(
355         String JavaDoc attributeName,
356         Class JavaDoc attributeType,
357         Object JavaDoc newValue) {
358
359         // clear the cached value
360
this.attributeCache.remove(attributeName);
361
362         if (attributeType.isArray()) {
363
364             // 1) Remove all old items from this array
365
Element collectionElement =
366                 getCollectionElement(attributeName, attributeType);
367
368             collectionElement.removeChildren(attributeName);
369
370             if (newValue != null) {
371                 // 2) Add all new items as entities
372
// using reflection here because we can't cast newValue into
373
// an Object[] because newValue may be an array of primitives
374
for (int i = 0; i < Array.getLength(newValue); i++) {
375                     Object JavaDoc obj = Array.get(newValue, i);
376                     addAttribute(
377                         collectionElement,
378                         attributeName,
379                         attributeType.getComponentType(),
380                         obj);
381                 }
382             }
383
384         } else if (Map JavaDoc.class.isAssignableFrom(attributeType)) {
385             Element collectionElement =
386                 getCollectionElement(attributeName, attributeType);
387
388             collectionElement.removeChildren(attributeName);
389             Map JavaDoc map = (Map JavaDoc) newValue;
390             for (Iterator JavaDoc mapIterator = map.entrySet().iterator();
391                     mapIterator.hasNext();) {
392
393
394                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) mapIterator.next();
395                 addAttribute(
396                     collectionElement,
397                     attributeName,
398                     super.getCollectionComponentType(attributeName),
399                     entry.getValue()).setAttribute(
400                         MAP_KEY_ATTRIBUTE, (String JavaDoc) entry.getKey());
401             }
402         } else {
403             Element attribute = this.element.getChild(attributeName);
404             if (attribute != null) {
405                 // clear the existing value
406
this.element.removeContent(attribute);
407             }
408             if (newValue != null) {
409                 addAttribute(this.element, attributeName, attributeType, newValue);
410             }
411         }
412     }
413
414     /**
415      * @inherit
416      * @see AbstractConfigurationProxy#addAttribute
417      */

418     public Element addAttribute(String JavaDoc attributeName, Class JavaDoc type, Object JavaDoc obj) {
419         // clear the cached value
420
this.attributeCache.remove(attributeName);
421
422         return addAttribute(
423             getCollectionElement(attributeName, Array JavaDoc.class),
424             attributeName,
425             type,
426             obj);
427     }
428
429     /**
430      * Adds an attribute.
431      *
432      * @param element the element to add an attribute to
433      * @param attributeName the name of the attribute to add
434      * @param type the classtype of the attribute
435      * @param obj the attribute to add
436      * @return the newly created images
437      */

438     protected Element addAttribute(
439             Element element, String JavaDoc attributeName, Class JavaDoc type, Object JavaDoc obj) {
440
441         // create the new attribute
442
Element newElement = constructElement(attributeName, type, obj);
443         // add it
444
element.addContent(newElement);
445         return newElement;
446     }
447
448     /**
449      * @inherit
450      * @see AbstractConfigurationProxy#setArrayValue
451      */

452     public void setArrayValue(
453         String JavaDoc attributeName,
454         Class JavaDoc attributeType,
455         int index,
456         Object JavaDoc value) {
457
458         // clear the cached value
459
this.attributeCache.remove(attributeName);
460
461         Element collectionElement =
462             getCollectionElement(attributeName, Array JavaDoc.class);
463
464         List JavaDoc children = collectionElement.getChildren(attributeName);
465
466         // check for an out of bounds index
467
if (index < 0 || index >= children.size()) {
468             throw new InvalidParameterException(
469                 this.getClass(),
470                 "Index out of bounds: Configuration name ["
471                     + this.getConfigurationName()
472                     + "], Attribute name ["
473                     + attributeName
474                     + "], size ["
475                     + children.size()
476                     + "], requested index ["
477                     + index
478                     + "]");
479         }
480
481         if (value == null) {
482             // the value is null, so remove it from the document
483
children.remove(index);
484
485         } else {
486             // create the new value
487
Element newElement =
488                 constructElement(attributeName, attributeType, value);
489             // add it
490
children.set(index, newElement);
491         }
492     }
493
494     /**
495      * <P>Sets the value of a specific index in an array of data in this
496      * configuration object. Implementing classes must set the specified index
497      * of the array named by <code>attributeName</code> to the specified value.
498      * </P>
499      *
500      * @param attributeName the name of the array to alter
501      * @param attributeType the component type of the value within the map.
502      * @param key the key within the map to set
503      * @param value the Object to set as the value of this indicie
504      */

505     public void setMapValue(
506         String JavaDoc attributeName,
507         Class JavaDoc attributeType,
508         Object JavaDoc key,
509         Object JavaDoc value) {
510
511         // clear the cached value
512
this.attributeCache.remove(attributeName);
513
514         Element collectionElement =
515             getCollectionElement(attributeName, Map JavaDoc.class);
516
517         List JavaDoc children = collectionElement.getChildren(attributeName);
518
519         int oldIndex = -1;
520         // If the value exists, remove it...
521
// It is not required to exists as this interface has the same general
522
// symantics as Map.put.
523
for (int i = 0; i < children.size(); i++) {
524             Element contentElement = (Element) children.get(i);
525             if (key.equals(contentElement.getAttributeValue(MAP_KEY_ATTRIBUTE))) {
526                 oldIndex = i;
527             }
528         }
529
530
531         if (value == null) {
532             // Do not add null values (maps don't support them anyway
533
} else {
534             // create the new value
535
Element newElement =
536                 constructElement(attributeName, attributeType, value);
537             newElement.setAttribute(MAP_KEY_ATTRIBUTE, (String JavaDoc) key);
538             // add it
539
if (oldIndex >= 0) {
540                 children.set(oldIndex, newElement);
541             } else {
542                 children.add(newElement);
543             }
544         }
545     }
546
547     /**
548      * Gets the element containing either an array or Map object.
549      *
550      * @param attributeName name of the attribute to get the element for
551      * @param type the type of class contained in the array or Map
552      * @return element containing the array or Map
553      */

554     protected Element getCollectionElement(String JavaDoc attributeName, Class JavaDoc type) {
555         String JavaDoc tagName = attributeName;
556         if (Map JavaDoc.class.isAssignableFrom(type)) {
557             tagName += "Map";
558         } else {
559             tagName += "Array";
560         }
561
562         Element collectionElement = this.element.getChild(tagName);
563         if (collectionElement == null) {
564             collectionElement = new Element(tagName);
565             this.element.addContent(collectionElement);
566         }
567         return collectionElement;
568     }
569
570     /**
571      * Converts the element to its object representation.
572      * <p>
573      * This implementation will create a new configuration proxy if the
574      * type is a subtype of Configuration or the type is complex.
575      * If it is not a configuration subtype, the type handlers
576      * will be used to convert the data to an object. If type is
577      * not a Configuration or complex type and element is null or its type
578      * handler returns null, this implementation will attempt to lookup a
579      * default via a call to lookupDefaultAttributeValue from the super class.
580      * If element is null or its type handler returns null and there is no
581      * default and type is a primitive, an exception is thrown.
582      *
583      * @param element the element to be converted
584      * @param name the name of the element, used to name the resulting
585      * object when it is a subtype of Configuration
586      * @param type expected type of the resulting object
587      * @return Object the object representation of element
588      *
589      * @throws ConfigurationFormatException indicates an error
590      * formatting the element
591      */

592     protected Object JavaDoc formatElement(Element element, String JavaDoc name, Class JavaDoc type)
593         throws ConfigurationFormatException {
594
595         try {
596             Object JavaDoc formattedElement = null;
597
598             if (Configuration.class.isAssignableFrom(type)) {
599                 // the data is a child configuration
600
if (element != null) {
601                     formattedElement =
602                         getChildConfiguration(
603                             type,
604                             element,
605                             getConfigurationName() + Node.DELIMITER + name);
606                 }
607
608             } else if (this.typeService.isComplexType(type)) {
609                 // the data needs to be extracted as a configuration
610
// then passed to a type handler
611
Configuration subConfiguration = null;
612
613                 if (element != null) {
614                     subConfiguration =
615                         getChildConfiguration(
616                             this.typeService.getRequiredConfigurationInterface(
617                                 type),
618                             element,
619                             getConfigurationName() + Node.DELIMITER + name);
620                 }
621
622                 // the type is complex so convert the sub configuration to
623
// an object
624
formattedElement =
625                     this.typeService.toObject(type, subConfiguration);
626
627             } else {
628                 // the data can be extracted as text
629
if (element != null) {
630                     formattedElement =
631                         this.typeService.toObject(type, element.getText());
632                 }
633                 // if the attribute is null and it is not a Configuration type,
634
// lookup the default
635
if (formattedElement == null) {
636
637                     formattedElement =
638                         lookupDefaultAttributeValue(
639                             this.getConfigurationInterface(),
640                             name,
641                             type);
642
643                     if ((formattedElement == null) && type.isPrimitive()) {
644                         // Be careful not to return null from methods that
645
// return primitives
646
throw new InvalidConfigurationException(
647                             this.getClass(),
648                             this.getConfigurationName(),
649                             name,
650                             "Primitive configurations must have values as "
651                                 + "they cannot return null.");
652                     }
653                 }
654             }
655
656             return formattedElement;
657
658         } catch (ConfigurationTypeException cte) {
659             throw new ConfigurationFormatException(
660                 this.getClass(),
661                 "Could not parse config data in document ["
662                     + getConfigurationName()
663                     + "], attribute name ["
664                     + name
665                     + "], expected type ["
666                     + type.getName()
667                     + "] data was ["
668                     + element.getText()
669                     + "]",
670                 cte);
671         }
672     }
673
674     /**
675      * Converts the element to its object representation.
676      * <p>
677      * The method is the same as formatElement except that it does not
678      * look up a default for the element if it is null.
679      *
680      * @param element the element to be converted
681      * @param name the name of the element, used to name the resulting
682      * object when it is a subtype of Configuration
683      * @param key the accessor key of the element, used to name the resulting
684      * object when it is a subtype of Configuration
685      * @param type expected type of the resulting object
686      * @return Object the object representation of element
687      *
688      * @throws ConfigurationFormatException indicates an error formatting
689      * the contained element
690      */

691     protected Object JavaDoc formatContainedElement(
692         Element element,
693         String JavaDoc name,
694         String JavaDoc key,
695         Class JavaDoc type)
696         throws ConfigurationFormatException {
697
698         try {
699             Object JavaDoc formattedElement = null;
700
701             if (Configuration.class.isAssignableFrom(type)) {
702                 // the data is an indexed child configuraiton
703
if (element != null) {
704                     formattedElement =
705                         getChildConfiguration(
706                             type,
707                             element,
708                             getConfigurationName()
709                                 + Node.DELIMITER
710                                 + name
711                                 + "["
712                                 + key
713                                 + "]");
714
715                 }
716
717             } else if (this.typeService.isComplexType(type)) {
718                 // the data needs to be extracted as a configuration
719
// then passed to a type handler
720
Configuration subConfiguration = null;
721
722                 if (element != null) {
723                     subConfiguration =
724                         getChildConfiguration(
725                             this.typeService.getRequiredConfigurationInterface(
726                                 type),
727                             element,
728                             getConfigurationName()
729                                 + Node.DELIMITER
730                                 + name
731                                 + "["
732                                 + key
733                                 + "]");
734
735                 }
736
737                 formattedElement =
738                     this.typeService.toObject(type, subConfiguration);
739
740             } else {
741                 if (element != null) {
742                     formattedElement =
743                         this.typeService.toObject(type, element.getText());
744                 }
745                 if ((formattedElement == null) && type.isPrimitive()) {
746                     // Be careful not to return null from methods that
747
// return primitives
748
throw new InvalidConfigurationException(
749                         this.getClass(),
750                         this.getConfigurationName(),
751                         name,
752                         "Primitive configurations must have values as "
753                             + "they cannot return null.");
754                 }
755             }
756
757             return formattedElement;
758         } catch (ConfigurationTypeException cte) {
759             throw new ConfigurationFormatException(
760                 this.getClass(),
761                 "Could not parse config data in document ["
762                     + getConfigurationName()
763                     + "], attribute name ["
764                     + name
765                     + "] index ["
766                     + key
767                     + "], expected type ["
768                     + type.getName()
769                     + "] data was ["
770                     + element.getText()
771                     + "]",
772                 cte);
773         }
774     }
775
776     /**
777      * <P>Builds a new implementation of the specified configuration class by
778      * using a JDOMConfigurationProxy as the InvocationHandler for a
779      * Dynamic Proxy.</P>
780      * <p>If the configuration is a reference (the element text starts with
781      * "ref://"), the referenced configuraiton is fetched from the config
782      * service and returned, otherwise, a new configuration object is created.
783      * </p>
784      *
785      * @param requiredInterface The class that will be implemented by
786      * the configuration
787      * @param element The root element of the configuration object
788      * @param name the name of the child configuration being retreived
789      * @return the new configuration object implementation representing the
790      * data supplied in the element
791      */

792     protected Configuration getChildConfiguration(
793         Class JavaDoc requiredInterface,
794         Element element,
795         String JavaDoc name) {
796
797         Configuration subConfiguration;
798
799         String JavaDoc textValue = element.getTextTrim();
800         if (textValue.startsWith(REF_PREFIX)) {
801             // the data is a reference to another configuration, fetch the
802
// configuration
803
if (this.isConfigurationWritable()) {
804                 subConfiguration =
805                     Config.getInstance().fetchWritableConfiguration(
806                         textValue.substring(REF_PREFIX_LENGTH));
807             } else {
808                 subConfiguration =
809                     Config.getInstance().fetchConfiguration(
810                         textValue.substring(REF_PREFIX_LENGTH));
811             }
812         } else {
813
814             // not a reference, construct a new proxy
815
Class JavaDoc configType =
816                 getConfigurationInterface(element, requiredInterface);
817
818             JDOMConfigurationProxy invocationHandler =
819                 new JDOMConfigurationProxy(element, configType);
820             invocationHandler.setConfigurationName(name);
821             Object JavaDoc proxy =
822                 Proxy.newProxyInstance(
823                     configType.getClassLoader(),
824                     new Class JavaDoc[] {configType},
825                     invocationHandler);
826
827             subConfiguration = (Configuration) proxy;
828
829             if (!isConfigurationWritable()) {
830                 subConfiguration.setConfigurationReadOnly();
831             }
832         }
833
834         // ensure that the actual type is indeed the right type
835
if (!requiredInterface.isAssignableFrom(subConfiguration.getClass())) {
836
837             throw new InvalidConfigurationException(
838                 this.getClass(),
839                 getConfigurationName(),
840                 element.getName(),
841                 "Required interface was not assignable from configured "
842                     + "interface, required ["
843                     + requiredInterface.getName()
844                     + "] configured ["
845                     + subConfiguration.getConfigurationInterface()
846                     + "]");
847         }
848
849         return subConfiguration;
850     }
851
852     /**
853      * Constructs a jdom element from the given object by calling the
854      * ConfigurationTypeService
855      *
856      * @param entityName name of the element to construct
857      * @param type type of data represented by obj
858      * @param obj data represented by returned element
859      * @return Element jdom element representation of obj
860      */

861     protected Element constructElement(
862         String JavaDoc entityName,
863         Class JavaDoc type,
864         Object JavaDoc obj) {
865
866         Element newElement;
867         if (Configuration.class.isAssignableFrom(type)) {
868             // It is a sub configuration type, convert it to an element
869
newElement = configurationToElement((Configuration) obj);
870             newElement.setName(entityName);
871
872         } else if (this.typeService.isComplexType(type)) {
873             // complex type, convert it to a configuration then to an element
874
try {
875                 Configuration configValue =
876                     this.typeService.toConfiguration(type, obj);
877                 newElement = configurationToElement(configValue);
878                 newElement.setName(entityName);
879
880             } catch (ConfigurationTypeException cte) {
881                 throw new InvalidConfigurationException(
882                     this.getClass(),
883                     getConfigurationName(),
884                     entityName,
885                     "Could not convert object to configuration",
886                     cte);
887             }
888
889         } else {
890             // simple type, create a simple element and set its text
891
newElement = new Element(entityName);
892             newElement.setText(this.typeService.toString(type, obj));
893         }
894         return newElement;
895     }
896
897     /**
898      * Converts a configuration to its jdom representation. If config is a
899      * nested configuration (either its name is null or its name starts with
900      * this configuration's name), a clone of its root element is returned.
901      * If config exists outside of this configuration, a reference to config
902      * is created.
903      *
904      * @param config configuration to be converted
905      * @return Element jdom represnetation
906      */

907     protected Element configurationToElement(Configuration config) {
908         boolean isNested;
909
910         Element configElement;
911
912         if (config.getConfigurationName() == null) {
913             // nested
914
configElement = (Element) config.getRootElement().clone();
915
916         } else if (getConfigurationName() == null) {
917             // not nested
918
configElement = new Element("Configuration");
919             configElement.setText(REF_PREFIX + config.getConfigurationName());
920
921         } else if (
922             config.getConfigurationName().startsWith(
923                 getConfigurationName() + Node.DELIMITER)) {
924             // nested
925
configElement = (Element) config.getRootElement().clone();
926
927         } else {
928             // not nested
929
configElement = new Element("Configuration");
930             configElement.setText(REF_PREFIX + config.getConfigurationName());
931         }
932
933         return configElement;
934     }
935
936     /**
937      * @inherit
938      */

939     public void setConfigurationReadOnly() {
940         super.setConfigurationReadOnly();
941 // // this object should be prepared for multithreaded reads,
942
// // the attributeCache needs to by synced to ensure that concurrent
943
// // reads to not corrupt the Map
944
// this.attributeCache =
945
// Collections.synchronizedMap(this.attributeCache);
946

947 // commented out until preLoadAttributeCache works
948
preLoadAttributeCache();
949     }
950
951     /**
952      * <b>
953      * This method is not in use as of carbon 1.1. It causes an infinite
954      * loop in the case of circular references within configs. Until a
955      * better solution is found for preloading configuration, the
956      * attributeCache is synchronized when the config is read-only.
957      * </b>
958      * <p>
959      * Loads all the attributes into the attribute cache. This is used when
960      * the configuration object is switched to read-only mode to prevent
961      * concurrent reads from corrupting the cache. After this, there should
962      * never be a lookup that needs to add to the cache; the cache effectively
963      * becomes read-only.
964      * <p>
965      * This implementation uses the Introspector to get all the properties
966      * of the configuration interface, then looks up each property using either
967      * lookupAttribute or getArray, depending on whether or not the property
968      * is indexed.
969      *
970      * @since carbon 1.1
971      */

972     protected void preLoadAttributeCache() {
973
974         try {
975             Class JavaDoc configInterface = getConfigurationInterface();
976
977             PropertyDescriptor JavaDoc[] properties =
978                 Introspector
979                     .getBeanInfo(configInterface)
980                     .getPropertyDescriptors();
981
982             for (int i = 0; i < properties.length; i++) {
983                 try {
984                     PropertyDescriptor JavaDoc thisProperty = properties[i];
985
986                     String JavaDoc propertyName =
987                         StringUtil.capitalize(thisProperty.getName());
988
989                     Class JavaDoc propertyType = thisProperty.getPropertyType();
990
991                     if (thisProperty instanceof IndexedPropertyDescriptor JavaDoc
992                         || propertyType.isArray()) {
993
994                         getArray(propertyName, propertyType.getComponentType());
995
996                     } else if (Map JavaDoc.class.isAssignableFrom(propertyType)) {
997                         getMap(
998                             propertyName,
999                             getCollectionComponentType(propertyName));
1000                            
1001                    } else {
1002                        lookupAttribute(propertyName, propertyType);
1003                    }
1004    
1005
1006// test logging commented out because it was too verbose (bug 434)
1007
// TODO: add white box testing to test caching functionality
1008
// if (Logger
1009
// .getLogger()
1010
// .isLogging(source, SeverityEnum.TRACE)) {
1011
//
1012
// Logger.getLogger().log(
1013
// source,
1014
// SeverityEnum.TRACE,
1015
// "Preloaded attribute ["
1016
// + propertyName
1017
// + "] of type ["
1018
// + thisProperty.getPropertyType()
1019
// + "] with value ["
1020
// + value
1021
// + "]");
1022
// }
1023

1024                } catch (InvalidConfigurationException ice) {
1025                    // don't care now, cache everything that is cachable
1026
// getting this exception means the value is somehow
1027
// invalid and won't ever be cached by this object.
1028
// If this is really a problem, the next person who asks
1029
// for this property will get the exception and perhaps
1030
// be able to give more context.
1031
}
1032            }
1033        } catch (IntrospectionException JavaDoc ie) {
1034            throw new IllegalStateException JavaDoc(
1035                this.getClass(),
1036                "Caught IntrospectionException which should not ever happen",
1037                ie);
1038        }
1039    }
1040
1041}
Popular Tags