001: /*
002: * The contents of this file are subject to the Sapient Public License
003: * Version 1.0 (the "License"); you may not use this file except in compliance
004: * with the License. You may obtain a copy of the License at
005: * http://carbon.sf.net/License.html.
006: *
007: * Software distributed under the License is distributed on an "AS IS" basis,
008: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
009: * the specific language governing rights and limitations under the License.
010: *
011: * The Original Code is The Carbon Component Framework.
012: *
013: * The Initial Developer of the Original Code is Sapient Corporation
014: *
015: * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
016: */
017:
018: package org.sape.carbon.core.config.format.jdom;
019:
020: import java.beans.IndexedPropertyDescriptor;
021: import java.beans.IntrospectionException;
022: import java.beans.Introspector;
023: import java.beans.PropertyDescriptor;
024: import java.lang.reflect.Array;
025: import java.lang.reflect.Proxy;
026: import java.util.HashMap;
027: import java.util.Iterator;
028: import java.util.List;
029: import java.util.Map;
030:
031: import org.sape.carbon.core.config.Config;
032: import org.sape.carbon.core.config.Configuration;
033: import org.sape.carbon.core.config.InvalidConfigurationException;
034: import org.sape.carbon.core.config.format.AbstractConfigurationProxy;
035: import org.sape.carbon.core.config.format.ConfigurationFormatException;
036: import org.sape.carbon.core.config.node.Node;
037: import org.sape.carbon.core.config.type.ConfigurationTypeException;
038: import org.sape.carbon.core.config.type.ConfigurationTypeService;
039: import org.sape.carbon.core.config.type.ConfigurationTypeServiceFactory;
040: import org.sape.carbon.core.exception.IllegalStateException;
041: import org.sape.carbon.core.exception.InvalidParameterException;
042: import org.sape.carbon.core.util.string.StringUtil;
043:
044: import org.jdom.Document;
045: import org.jdom.Element;
046:
047: /**
048: * <p>This is an extension of an <code>AbstractConfigurationProxy</code>
049: * that is backed by a JDOM Document data structure.
050: * </p>
051: *
052: * Copyright 2002 Sapient
053: * @since carbon 1.0
054: * @author Greg Hinkle, February 2002
055: * @author Douglas Voet
056: * @version $Revision: 1.52 $($Author: dvoet $ / $Date: 2003/09/24 14:09:55 $)
057: */
058: public class JDOMConfigurationProxy extends AbstractConfigurationProxy {
059: /** Holds the type service for configuration. */
060: protected final ConfigurationTypeService typeService = ConfigurationTypeServiceFactory
061: .getInstance();
062:
063: /**
064: * Map for caching previously accessed attributes
065: * @since carbon 1.1
066: */
067: protected Map attributeCache = new HashMap();
068:
069: /** Key attribute for a map element. */
070: public static final String MAP_KEY_ATTRIBUTE = "Key";
071:
072: /**
073: * Constructs a JDOMConfigurationProxy for the specified
074: * class type. This class only supports subclasses of the
075: * {@link org.sape.carbon.core.config.Configuration} object.
076: * @param configurationClass The type of Configuration object that should be
077: * returned
078: */
079: public JDOMConfigurationProxy(Class configurationClass) {
080: super (configurationClass);
081: }
082:
083: /**
084: * Constructs a JDOMConfigurationProxy for the specified
085: * class type. This class only supports subclasses of the
086: * {@link org.sape.carbon.core.config.Configuration} object.
087: * The supplied document is used as the backing store for
088: * this configuration object's data.
089: * @param document The JDOM Document object representing a configurations
090: * data
091: * @param configurationClass The type of configuration to be implemented
092: */
093: public JDOMConfigurationProxy(Document document,
094: Class configurationClass) {
095:
096: super (document, document.getRootElement(), configurationClass);
097: }
098:
099: /**
100: * Constructs a JDOMConfigurationProxy for the specified
101: * class type. This class only supports subclasses of the
102: * {@link org.sape.carbon.core.config.Configuration} object.
103: * The supplied Element is used as the backing datastore
104: * for this configuration object and it's document object
105: * will be modified when this object is modified.
106: * @param element The JDOM element object representing to root node
107: * of this configuration object
108: * @param configurationClass The type of configuration object that will be
109: * implemented
110: */
111: public JDOMConfigurationProxy(Element element,
112: Class configurationClass) {
113:
114: super (null, element, configurationClass);
115: }
116:
117: /**
118: * <P>This method overrides the base proxy's <code>toString</code>
119: * implementation so that useful data is printed out when toString is
120: * called.
121: * </P>
122: *
123: * @param proxy the proxy to print
124: * @return a string representation of this configuration object
125: */
126: public String proxyToString(Object proxy) {
127: return this .getDocumentType().getName() + " ["
128: + this .getConfigurationName() + "]";
129: }
130:
131: /**
132: * <P>This method clones this Configuration object through a deep-copy of
133: * it's underlying datastructure. The resulting configuration object is
134: * a new object and can be altered without affecting the original.</P>
135: *
136: * @return Object the cloned configuration object
137: */
138: public Object clone() {
139:
140: JDOMConfigurationProxy proxy = null;
141:
142: if (this .document == null) {
143: // Expected for nested configurations
144: Element newElement = (Element) super .element.clone();
145:
146: // Manually construct the invocation handler and rewrap it.
147: // This is done seperately because "newProxy()" would only use
148: // the root element and would loose document information such
149: // as the encoding type.
150: proxy = new JDOMConfigurationProxy(newElement, this
151: .getDocumentType());
152:
153: } else {
154: Document doc = (Document) this .document.clone();
155:
156: // Manually construct the invocation handler and rewrap it.
157: // This is done seperately because "newProxy()" would only use
158: // the root element and would loose document information such
159: // as the encoding type.
160: proxy = new JDOMConfigurationProxy(doc, this
161: .getDocumentType());
162: }
163:
164: proxy.setConfigurationName(this .getConfigurationName());
165:
166: Configuration config = (Configuration) Proxy.newProxyInstance(
167: this .getClass().getClassLoader(), new Class[] { this
168: .getDocumentType() }, proxy);
169:
170: return config;
171: }
172:
173: /**
174: * @inherit
175: * @see AbstractConfigurationProxy#lookupAttribute
176: */
177: public Object lookupAttribute(String attributeName, Class returnType) {
178: Object attributeValue = this .attributeCache.get(attributeName);
179: if (attributeValue == null) {
180:
181: try {
182: // lookup the element in the document
183: Element childElement = this .element
184: .getChild(attributeName);
185:
186: // format and return the element
187: attributeValue = formatElement(childElement,
188: attributeName, returnType);
189:
190: if (this .typeService.isCacheableType(returnType)) {
191: this .attributeCache.put(attributeName,
192: attributeValue);
193: }
194:
195: } catch (ConfigurationFormatException cfe) {
196: throw new InvalidConfigurationException(
197: this .getClass(), this .getConfigurationName(),
198: attributeName,
199: "Format error on configuration value.", cfe);
200: }
201: }
202:
203: return attributeValue;
204: }
205:
206: /**
207: * <P>Retrieves an array of objects of the specified type that have
208: * the specified name. This might get a list of dependent objects or
209: * a list of string values.</P>
210: *
211: * @param attributeName the name of the array to retrieve
212: * @param componentType the type of the objects that should be retrieved
213: * within the array
214: * @return an array of objects that match the name and type specified
215: */
216: public Object getArray(String attributeName, Class componentType) {
217:
218: Object attributeArray = this .attributeCache.get(attributeName);
219: if (attributeArray == null) {
220: Element collectionElement = getCollectionElement(
221: attributeName, Array.class);
222:
223: List list = collectionElement.getChildren(attributeName);
224: int length = list.size();
225:
226: attributeArray = Array.newInstance(componentType, length);
227:
228: try {
229: for (int i = 0; i < length; i++) {
230: // get the next element
231: Element element = (Element) list.get(i);
232:
233: // format it
234: Object attribute = formatContainedElement(element,
235: attributeName, String.valueOf(i),
236: componentType);
237:
238: // add it to the array
239: Array.set(attributeArray, i, attribute);
240: }
241: if (this .typeService.isCacheableType(componentType)) {
242: this .attributeCache.put(attributeName,
243: attributeArray);
244: }
245: } catch (ConfigurationFormatException cfe) {
246: throw new InvalidConfigurationException(
247: this .getClass(), this .getConfigurationName(),
248: attributeName,
249: "Format error on configuration value.", cfe);
250: }
251: }
252: return attributeArray;
253: }
254:
255: /**
256: * @inherit
257: * @see AbstractConfigurationProxy#getMap
258: */
259: public Map getMap(String tagName, Class contentType) {
260:
261: Map attributeMap = (Map) this .attributeCache.get(tagName);
262: if (attributeMap == null) {
263:
264: Element collectionElement = getCollectionElement(tagName,
265: Map.class);
266:
267: List list = collectionElement.getChildren(tagName);
268: int length = list.size();
269:
270: attributeMap = new HashMap();
271:
272: try {
273: for (int i = 0; i < length; i++) {
274: // get the next element
275: Element element = (Element) list.get(i);
276: String unformattedKey = element
277: .getAttributeValue(MAP_KEY_ATTRIBUTE);
278:
279: if (unformattedKey == null) {
280: throw new InvalidConfigurationException(
281: this .getClass(),
282: this .getConfigurationName(),
283: tagName,
284: "All map entries in configuration must "
285: + "have a valid [Key] attribute. (And "
286: + "its case sensitive)");
287: }
288:
289: // format key as a String
290: // at this point, this is done only so that token
291: // replacement will be performed for keys, if in the
292: // future, we want to support key objects other than
293: // Strings, replace the hard coded String.class with the
294: // appropriate Class object and the type service will do
295: // the rest
296: String key = (String) this .typeService.toObject(
297: String.class, unformattedKey);
298:
299: // format it
300: Object attribute = formatContainedElement(element,
301: tagName, key, contentType);
302:
303: // add it to the map
304: if (this .typeService.isCacheableType(contentType)) {
305: attributeMap.put(key, attribute);
306: }
307: }
308: } catch (NullPointerException npe) {
309: throw new InvalidConfigurationException(
310: this .getClass(), this .getConfigurationName(),
311: tagName,
312: "Map tags must have keys specified by the "
313: + MAP_KEY_ATTRIBUTE + " attribute.",
314: npe);
315: } catch (ConfigurationFormatException cfe) {
316:
317: throw new InvalidConfigurationException(
318: this .getClass(), this .getConfigurationName(),
319: tagName,
320: "Format error on configuration value.", cfe);
321: } catch (ConfigurationTypeException cte) {
322:
323: throw new InvalidConfigurationException(
324: this .getClass(), this .getConfigurationName(),
325: tagName, "Format error on configuration key.",
326: cte);
327: }
328: this .attributeCache.put(tagName, attributeMap);
329: }
330: return attributeMap;
331: }
332:
333: /**
334: * @inherit
335: * @see AbstractConfigurationProxy#alterAttribute
336: */
337: public void alterAttribute(String attributeName,
338: Class attributeType, Object newValue) {
339:
340: // clear the cached value
341: this .attributeCache.remove(attributeName);
342:
343: if (attributeType.isArray()) {
344:
345: // 1) Remove all old items from this array
346: Element collectionElement = getCollectionElement(
347: attributeName, attributeType);
348:
349: collectionElement.removeChildren(attributeName);
350:
351: if (newValue != null) {
352: // 2) Add all new items as entities
353: // using reflection here because we can't cast newValue into
354: // an Object[] because newValue may be an array of primitives
355: for (int i = 0; i < Array.getLength(newValue); i++) {
356: Object obj = Array.get(newValue, i);
357: addAttribute(collectionElement, attributeName,
358: attributeType.getComponentType(), obj);
359: }
360: }
361:
362: } else if (Map.class.isAssignableFrom(attributeType)) {
363: Element collectionElement = getCollectionElement(
364: attributeName, attributeType);
365:
366: collectionElement.removeChildren(attributeName);
367: Map map = (Map) newValue;
368: for (Iterator mapIterator = map.entrySet().iterator(); mapIterator
369: .hasNext();) {
370:
371: Map.Entry entry = (Map.Entry) mapIterator.next();
372: addAttribute(
373: collectionElement,
374: attributeName,
375: super .getCollectionComponentType(attributeName),
376: entry.getValue()).setAttribute(
377: MAP_KEY_ATTRIBUTE, (String) entry.getKey());
378: }
379: } else {
380: Element attribute = this .element.getChild(attributeName);
381: if (attribute != null) {
382: // clear the existing value
383: this .element.removeContent(attribute);
384: }
385: if (newValue != null) {
386: addAttribute(this .element, attributeName,
387: attributeType, newValue);
388: }
389: }
390: }
391:
392: /**
393: * @inherit
394: * @see AbstractConfigurationProxy#addAttribute
395: */
396: public Element addAttribute(String attributeName, Class type,
397: Object obj) {
398: // clear the cached value
399: this .attributeCache.remove(attributeName);
400:
401: return addAttribute(getCollectionElement(attributeName,
402: Array.class), attributeName, type, obj);
403: }
404:
405: /**
406: * Adds an attribute.
407: *
408: * @param element the element to add an attribute to
409: * @param attributeName the name of the attribute to add
410: * @param type the classtype of the attribute
411: * @param obj the attribute to add
412: * @return the newly created images
413: */
414: protected Element addAttribute(Element element,
415: String attributeName, Class type, Object obj) {
416:
417: // create the new attribute
418: Element newElement = constructElement(attributeName, type, obj);
419: // add it
420: element.addContent(newElement);
421: return newElement;
422: }
423:
424: /**
425: * @inherit
426: * @see AbstractConfigurationProxy#setArrayValue
427: */
428: public void setArrayValue(String attributeName,
429: Class attributeType, int index, Object value) {
430:
431: // clear the cached value
432: this .attributeCache.remove(attributeName);
433:
434: Element collectionElement = getCollectionElement(attributeName,
435: Array.class);
436:
437: List children = collectionElement.getChildren(attributeName);
438:
439: // check for an out of bounds index
440: if (index < 0 || index >= children.size()) {
441: throw new InvalidParameterException(this .getClass(),
442: "Index out of bounds: Configuration name ["
443: + this .getConfigurationName()
444: + "], Attribute name [" + attributeName
445: + "], size [" + children.size()
446: + "], requested index [" + index + "]");
447: }
448:
449: if (value == null) {
450: // the value is null, so remove it from the document
451: children.remove(index);
452:
453: } else {
454: // create the new value
455: Element newElement = constructElement(attributeName,
456: attributeType, value);
457: // add it
458: children.set(index, newElement);
459: }
460: }
461:
462: /**
463: * <P>Sets the value of a specific index in an array of data in this
464: * configuration object. Implementing classes must set the specified index
465: * of the array named by <code>attributeName</code> to the specified value.
466: * </P>
467: *
468: * @param attributeName the name of the array to alter
469: * @param attributeType the component type of the value within the map.
470: * @param key the key within the map to set
471: * @param value the Object to set as the value of this indicie
472: */
473: public void setMapValue(String attributeName, Class attributeType,
474: Object key, Object value) {
475:
476: // clear the cached value
477: this .attributeCache.remove(attributeName);
478:
479: Element collectionElement = getCollectionElement(attributeName,
480: Map.class);
481:
482: List children = collectionElement.getChildren(attributeName);
483:
484: int oldIndex = -1;
485: // If the value exists, remove it...
486: // It is not required to exists as this interface has the same general
487: // symantics as Map.put.
488: for (int i = 0; i < children.size(); i++) {
489: Element contentElement = (Element) children.get(i);
490: if (key.equals(contentElement
491: .getAttributeValue(MAP_KEY_ATTRIBUTE))) {
492: oldIndex = i;
493: }
494: }
495:
496: if (value == null) {
497: // Do not add null values (maps don't support them anyway
498: } else {
499: // create the new value
500: Element newElement = constructElement(attributeName,
501: attributeType, value);
502: newElement.setAttribute(MAP_KEY_ATTRIBUTE, (String) key);
503: // add it
504: if (oldIndex >= 0) {
505: children.set(oldIndex, newElement);
506: } else {
507: children.add(newElement);
508: }
509: }
510: }
511:
512: /**
513: * Gets the element containing either an array or Map object.
514: *
515: * @param attributeName name of the attribute to get the element for
516: * @param type the type of class contained in the array or Map
517: * @return element containing the array or Map
518: */
519: protected Element getCollectionElement(String attributeName,
520: Class type) {
521: String tagName = attributeName;
522: if (Map.class.isAssignableFrom(type)) {
523: tagName += "Map";
524: } else {
525: tagName += "Array";
526: }
527:
528: Element collectionElement = this .element.getChild(tagName);
529: if (collectionElement == null) {
530: collectionElement = new Element(tagName);
531: this .element.addContent(collectionElement);
532: }
533: return collectionElement;
534: }
535:
536: /**
537: * Converts the element to its object representation.
538: * <p>
539: * This implementation will create a new configuration proxy if the
540: * type is a subtype of Configuration or the type is complex.
541: * If it is not a configuration subtype, the type handlers
542: * will be used to convert the data to an object. If type is
543: * not a Configuration or complex type and element is null or its type
544: * handler returns null, this implementation will attempt to lookup a
545: * default via a call to lookupDefaultAttributeValue from the super class.
546: * If element is null or its type handler returns null and there is no
547: * default and type is a primitive, an exception is thrown.
548: *
549: * @param element the element to be converted
550: * @param name the name of the element, used to name the resulting
551: * object when it is a subtype of Configuration
552: * @param type expected type of the resulting object
553: * @return Object the object representation of element
554: *
555: * @throws ConfigurationFormatException indicates an error
556: * formatting the element
557: */
558: protected Object formatElement(Element element, String name,
559: Class type) throws ConfigurationFormatException {
560:
561: try {
562: Object formattedElement = null;
563:
564: if (Configuration.class.isAssignableFrom(type)) {
565: // the data is a child configuration
566: if (element != null) {
567: formattedElement = getChildConfiguration(type,
568: element, getConfigurationName()
569: + Node.DELIMITER + name);
570: }
571:
572: } else if (this .typeService.isComplexType(type)) {
573: // the data needs to be extracted as a configuration
574: // then passed to a type handler
575: Configuration subConfiguration = null;
576:
577: if (element != null) {
578: subConfiguration = getChildConfiguration(
579: this .typeService
580: .getRequiredConfigurationInterface(type),
581: element, getConfigurationName()
582: + Node.DELIMITER + name);
583: }
584:
585: // the type is complex so convert the sub configuration to
586: // an object
587: formattedElement = this .typeService.toObject(type,
588: subConfiguration);
589:
590: } else {
591: // the data can be extracted as text
592: if (element != null) {
593: formattedElement = this .typeService.toObject(type,
594: element.getText());
595: }
596: // if the attribute is null and it is not a Configuration type,
597: // lookup the default
598: if (formattedElement == null) {
599:
600: formattedElement = lookupDefaultAttributeValue(this
601: .getConfigurationInterface(), name, type);
602:
603: if ((formattedElement == null)
604: && type.isPrimitive()) {
605: // Be careful not to return null from methods that
606: // return primitives
607: throw new InvalidConfigurationException(this
608: .getClass(), this
609: .getConfigurationName(), name,
610: "Primitive configurations must have values as "
611: + "they cannot return null.");
612: }
613: }
614: }
615:
616: return formattedElement;
617:
618: } catch (ConfigurationTypeException cte) {
619: throw new ConfigurationFormatException(this .getClass(),
620: "Could not parse config data in document ["
621: + getConfigurationName()
622: + "], attribute name [" + name
623: + "], expected type [" + type.getName()
624: + "] data was [" + element.getText() + "]",
625: cte);
626: }
627: }
628:
629: /**
630: * Converts the element to its object representation.
631: * <p>
632: * The method is the same as formatElement except that it does not
633: * look up a default for the element if it is null.
634: *
635: * @param element the element to be converted
636: * @param name the name of the element, used to name the resulting
637: * object when it is a subtype of Configuration
638: * @param key the accessor key of the element, used to name the resulting
639: * object when it is a subtype of Configuration
640: * @param type expected type of the resulting object
641: * @return Object the object representation of element
642: *
643: * @throws ConfigurationFormatException indicates an error formatting
644: * the contained element
645: */
646: protected Object formatContainedElement(Element element,
647: String name, String key, Class type)
648: throws ConfigurationFormatException {
649:
650: try {
651: Object formattedElement = null;
652:
653: if (Configuration.class.isAssignableFrom(type)) {
654: // the data is an indexed child configuraiton
655: if (element != null) {
656: formattedElement = getChildConfiguration(type,
657: element, getConfigurationName()
658: + Node.DELIMITER + name + "[" + key
659: + "]");
660:
661: }
662:
663: } else if (this .typeService.isComplexType(type)) {
664: // the data needs to be extracted as a configuration
665: // then passed to a type handler
666: Configuration subConfiguration = null;
667:
668: if (element != null) {
669: subConfiguration = getChildConfiguration(
670: this .typeService
671: .getRequiredConfigurationInterface(type),
672: element, getConfigurationName()
673: + Node.DELIMITER + name + "[" + key
674: + "]");
675:
676: }
677:
678: formattedElement = this .typeService.toObject(type,
679: subConfiguration);
680:
681: } else {
682: if (element != null) {
683: formattedElement = this .typeService.toObject(type,
684: element.getText());
685: }
686: if ((formattedElement == null) && type.isPrimitive()) {
687: // Be careful not to return null from methods that
688: // return primitives
689: throw new InvalidConfigurationException(this
690: .getClass(), this .getConfigurationName(),
691: name,
692: "Primitive configurations must have values as "
693: + "they cannot return null.");
694: }
695: }
696:
697: return formattedElement;
698: } catch (ConfigurationTypeException cte) {
699: throw new ConfigurationFormatException(this .getClass(),
700: "Could not parse config data in document ["
701: + getConfigurationName()
702: + "], attribute name [" + name
703: + "] index [" + key + "], expected type ["
704: + type.getName() + "] data was ["
705: + element.getText() + "]", cte);
706: }
707: }
708:
709: /**
710: * <P>Builds a new implementation of the specified configuration class by
711: * using a JDOMConfigurationProxy as the InvocationHandler for a
712: * Dynamic Proxy.</P>
713: * <p>If the configuration is a reference (the element text starts with
714: * "ref://"), the referenced configuraiton is fetched from the config
715: * service and returned, otherwise, a new configuration object is created.
716: * </p>
717: *
718: * @param requiredInterface The class that will be implemented by
719: * the configuration
720: * @param element The root element of the configuration object
721: * @param name the name of the child configuration being retreived
722: * @return the new configuration object implementation representing the
723: * data supplied in the element
724: */
725: protected Configuration getChildConfiguration(
726: Class requiredInterface, Element element, String name) {
727:
728: Configuration subConfiguration;
729:
730: String textValue = element.getTextTrim();
731: if (textValue.startsWith(REF_PREFIX)) {
732: // the data is a reference to another configuration, fetch the
733: // configuration
734: if (this .isConfigurationWritable()) {
735: subConfiguration = Config.getInstance()
736: .fetchWritableConfiguration(
737: textValue.substring(REF_PREFIX_LENGTH));
738: } else {
739: subConfiguration = Config.getInstance()
740: .fetchConfiguration(
741: textValue.substring(REF_PREFIX_LENGTH));
742: }
743: } else {
744:
745: // not a reference, construct a new proxy
746: Class configType = getConfigurationInterface(element,
747: requiredInterface);
748:
749: JDOMConfigurationProxy invocationHandler = new JDOMConfigurationProxy(
750: element, configType);
751: invocationHandler.setConfigurationName(name);
752: Object proxy = Proxy.newProxyInstance(configType
753: .getClassLoader(), new Class[] { configType },
754: invocationHandler);
755:
756: subConfiguration = (Configuration) proxy;
757:
758: if (!isConfigurationWritable()) {
759: subConfiguration.setConfigurationReadOnly();
760: }
761: }
762:
763: // ensure that the actual type is indeed the right type
764: if (!requiredInterface.isAssignableFrom(subConfiguration
765: .getClass())) {
766:
767: throw new InvalidConfigurationException(this .getClass(),
768: getConfigurationName(), element.getName(),
769: "Required interface was not assignable from configured "
770: + "interface, required ["
771: + requiredInterface.getName()
772: + "] configured ["
773: + subConfiguration
774: .getConfigurationInterface() + "]");
775: }
776:
777: return subConfiguration;
778: }
779:
780: /**
781: * Constructs a jdom element from the given object by calling the
782: * ConfigurationTypeService
783: *
784: * @param entityName name of the element to construct
785: * @param type type of data represented by obj
786: * @param obj data represented by returned element
787: * @return Element jdom element representation of obj
788: */
789: protected Element constructElement(String entityName, Class type,
790: Object obj) {
791:
792: Element newElement;
793: if (Configuration.class.isAssignableFrom(type)) {
794: // It is a sub configuration type, convert it to an element
795: newElement = configurationToElement((Configuration) obj);
796: newElement.setName(entityName);
797:
798: } else if (this .typeService.isComplexType(type)) {
799: // complex type, convert it to a configuration then to an element
800: try {
801: Configuration configValue = this .typeService
802: .toConfiguration(type, obj);
803: newElement = configurationToElement(configValue);
804: newElement.setName(entityName);
805:
806: } catch (ConfigurationTypeException cte) {
807: throw new InvalidConfigurationException(
808: this .getClass(), getConfigurationName(),
809: entityName,
810: "Could not convert object to configuration",
811: cte);
812: }
813:
814: } else {
815: // simple type, create a simple element and set its text
816: newElement = new Element(entityName);
817: newElement.setText(this .typeService.toString(type, obj));
818: }
819: return newElement;
820: }
821:
822: /**
823: * Converts a configuration to its jdom representation. If config is a
824: * nested configuration (either its name is null or its name starts with
825: * this configuration's name), a clone of its root element is returned.
826: * If config exists outside of this configuration, a reference to config
827: * is created.
828: *
829: * @param config configuration to be converted
830: * @return Element jdom represnetation
831: */
832: protected Element configurationToElement(Configuration config) {
833: boolean isNested;
834:
835: Element configElement;
836:
837: if (config.getConfigurationName() == null) {
838: // nested
839: configElement = (Element) config.getRootElement().clone();
840:
841: } else if (getConfigurationName() == null) {
842: // not nested
843: configElement = new Element("Configuration");
844: configElement.setText(REF_PREFIX
845: + config.getConfigurationName());
846:
847: } else if (config.getConfigurationName().startsWith(
848: getConfigurationName() + Node.DELIMITER)) {
849: // nested
850: configElement = (Element) config.getRootElement().clone();
851:
852: } else {
853: // not nested
854: configElement = new Element("Configuration");
855: configElement.setText(REF_PREFIX
856: + config.getConfigurationName());
857: }
858:
859: return configElement;
860: }
861:
862: /**
863: * @inherit
864: */
865: public void setConfigurationReadOnly() {
866: super .setConfigurationReadOnly();
867: // // this object should be prepared for multithreaded reads,
868: // // the attributeCache needs to by synced to ensure that concurrent
869: // // reads to not corrupt the Map
870: // this.attributeCache =
871: // Collections.synchronizedMap(this.attributeCache);
872:
873: // commented out until preLoadAttributeCache works
874: preLoadAttributeCache();
875: }
876:
877: /**
878: * <b>
879: * This method is not in use as of carbon 1.1. It causes an infinite
880: * loop in the case of circular references within configs. Until a
881: * better solution is found for preloading configuration, the
882: * attributeCache is synchronized when the config is read-only.
883: * </b>
884: * <p>
885: * Loads all the attributes into the attribute cache. This is used when
886: * the configuration object is switched to read-only mode to prevent
887: * concurrent reads from corrupting the cache. After this, there should
888: * never be a lookup that needs to add to the cache; the cache effectively
889: * becomes read-only.
890: * <p>
891: * This implementation uses the Introspector to get all the properties
892: * of the configuration interface, then looks up each property using either
893: * lookupAttribute or getArray, depending on whether or not the property
894: * is indexed.
895: *
896: * @since carbon 1.1
897: */
898: protected void preLoadAttributeCache() {
899:
900: try {
901: Class configInterface = getConfigurationInterface();
902:
903: PropertyDescriptor[] properties = Introspector.getBeanInfo(
904: configInterface).getPropertyDescriptors();
905:
906: for (int i = 0; i < properties.length; i++) {
907: try {
908: PropertyDescriptor this Property = properties[i];
909:
910: String propertyName = StringUtil
911: .capitalize(this Property.getName());
912:
913: Class propertyType = this Property.getPropertyType();
914:
915: if (this Property instanceof IndexedPropertyDescriptor
916: || propertyType.isArray()) {
917:
918: getArray(propertyName, propertyType
919: .getComponentType());
920:
921: } else if (Map.class.isAssignableFrom(propertyType)) {
922: getMap(
923: propertyName,
924: getCollectionComponentType(propertyName));
925:
926: } else {
927: lookupAttribute(propertyName, propertyType);
928: }
929:
930: // test logging commented out because it was too verbose (bug 434)
931: // TODO: add white box testing to test caching functionality
932: // if (Logger
933: // .getLogger()
934: // .isLogging(source, SeverityEnum.TRACE)) {
935: //
936: // Logger.getLogger().log(
937: // source,
938: // SeverityEnum.TRACE,
939: // "Preloaded attribute ["
940: // + propertyName
941: // + "] of type ["
942: // + thisProperty.getPropertyType()
943: // + "] with value ["
944: // + value
945: // + "]");
946: // }
947:
948: } catch (InvalidConfigurationException ice) {
949: // don't care now, cache everything that is cachable
950: // getting this exception means the value is somehow
951: // invalid and won't ever be cached by this object.
952: // If this is really a problem, the next person who asks
953: // for this property will get the exception and perhaps
954: // be able to give more context.
955: }
956: }
957: } catch (IntrospectionException ie) {
958: throw new IllegalStateException(
959: this .getClass(),
960: "Caught IntrospectionException which should not ever happen",
961: ie);
962: }
963: }
964:
965: }
|