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;
019:
020: import java.beans.BeanInfo;
021: import java.beans.IntrospectionException;
022: import java.beans.Introspector;
023: import java.beans.PropertyDescriptor;
024: import java.io.InputStream;
025: import java.io.OutputStream;
026: import java.lang.reflect.Array;
027: import java.lang.reflect.InvocationTargetException;
028: import java.lang.reflect.Method;
029: import java.lang.reflect.Proxy;
030: import java.util.HashSet;
031: import java.util.Iterator;
032: import java.util.Map;
033: import java.util.Set;
034:
035: import org.jdom.Document;
036:
037: import org.apache.commons.logging.Log;
038: import org.apache.commons.logging.LogFactory;
039:
040: import org.sape.carbon.core.config.Config;
041: import org.sape.carbon.core.config.Configuration;
042: import org.sape.carbon.core.config.ConfigurationRuntimeException;
043: import org.sape.carbon.core.config.InvalidConfigurationException;
044: import org.sape.carbon.core.config.PropertyConfiguration;
045: import org.sape.carbon.core.config.format.jdom.JDOMConfigurationFactory;
046: import org.sape.carbon.core.config.format.jdom.JDOMConfigurationProxy;
047: import org.sape.carbon.core.config.format.jdom.JDOMPropertyConfiguration;
048: import org.sape.carbon.core.config.node.Node;
049: import org.sape.carbon.core.config.type.ConfigurationTypeService;
050: import org.sape.carbon.core.config.type.ConfigurationTypeServiceFactory;
051: import org.sape.carbon.core.exception.ExceptionUtility;
052: import org.sape.carbon.core.exception.InvalidParameterException;
053: import org.sape.carbon.core.util.string.StringUtil;
054:
055: /**
056: * <P>This default implementation of the configuration format serivce provides
057: * an XML based configuration to implemented interface form of configurations.
058: * </P>
059: *
060: * Copyright 2002 Sapient
061: * @since carbon 1.0
062: * @author Greg Hinkle, January 2002
063: * @version $Revision: 1.34 $($Author: dvoet $ / $Date: 2003/07/29 18:57:25 $)
064: */
065: public class DefaultConfigurationFormatService implements
066: ConfigurationFormatService {
067:
068: /**
069: * Provides a handle to Apache-commons logger
070: */
071: private Log log = LogFactory.getLog(this .getClass());
072:
073: /**
074: * <p>
075: * Creates a new configuration object of the type specified.
076: * </p>
077: * @param configurationClass type of the configuration object to build
078: * @return the newly created configuration
079: */
080: public Configuration newConfiguration(Class configurationClass) {
081: if (!Configuration.class.isAssignableFrom(configurationClass)) {
082: throw new org.sape.carbon.core.exception.InvalidParameterException(
083: this .getClass(), "The supplied type ["
084: + configurationClass.getName()
085: + "] was not assignable from "
086: + Configuration.class.getName());
087: }
088:
089: if (configurationClass == PropertyConfiguration.class) {
090: return new JDOMPropertyConfiguration();
091:
092: } else {
093: JDOMConfigurationProxy proxy = new JDOMConfigurationProxy(
094: configurationClass);
095:
096: Configuration config = (Configuration) Proxy
097: .newProxyInstance(this .getClass().getClassLoader(),
098: new Class[] { configurationClass }, proxy);
099:
100: return config;
101: }
102: }
103:
104: /**
105: * <P>Loads a <code>Configuration</code> object from the given
106: * <code>InputStream</code>. This Configuration object will represent
107: * the full object-graph depiction of a live configuration.</P>
108: *
109: * @param name The name of the configuration node
110: * @param in the {@link java.io.InputStream} from which
111: * the configuration will be read
112: * @throws ConfigurationFormatException when there is a formatting error
113: * with the input stream
114: * @return The Configuration object representing a live
115: * object graph of the data from the input stream
116: */
117: public Configuration readConfigurationStream(String name,
118: InputStream in) throws ConfigurationFormatException {
119:
120: ConfigurationDataFormatService dataFormat = new JDOMConfigurationFactory();
121:
122: Document dataNode = dataFormat.readConfigurationStreamToData(
123: name, in);
124:
125: String className = null;
126: Class configurationClass = null;
127: try {
128: className = dataNode.getRootElement().getAttributeValue(
129: "ConfigurationInterface");
130:
131: if (className == null) {
132: throw new InvalidConfigurationException(
133: this .getClass(),
134: name,
135: "ConfigurationInterface",
136: "Document did not supply a valid ConfigurationInterface "
137: + "class name. All configuration documents must supply "
138: + "the Java interface which represents them.");
139: } else {
140: configurationClass = Class.forName(className);
141: }
142: } catch (ClassNotFoundException cnfe) {
143: throw new InvalidConfigurationException(this .getClass(),
144: name, "ConfigurationInterface",
145: "Document did not supply a valid ConfigurationInterface "
146: + "class name. The class [" + className
147: + "] could not be found.");
148: }
149:
150: Configuration config = null;
151:
152: if (configurationClass.equals(PropertyConfiguration.class)) {
153: String extendedConfigurationName = dataNode
154: .getRootElement().getAttributeValue("Extends");
155:
156: if (extendedConfigurationName == null) {
157: config = new JDOMPropertyConfiguration(dataNode);
158: } else {
159: try {
160: PropertyConfiguration extendedConfiguration = (PropertyConfiguration) Config
161: .getInstance().fetchConfiguration(
162: extendedConfigurationName);
163:
164: config = new JDOMPropertyConfiguration(dataNode,
165: extendedConfiguration);
166:
167: } catch (ConfigurationRuntimeException cre) {
168: throw new InvalidConfigurationException(this
169: .getClass(), name, "Extends",
170: "Could not reference parent configuration: ["
171: + extendedConfigurationName + "]",
172: cre);
173:
174: } catch (ClassCastException cce) {
175: throw new InvalidConfigurationException(this
176: .getClass(), name, "Extends",
177: "Parent was not assignable from "
178: + PropertyConfiguration.class
179: .getClass(), cce);
180: }
181: }
182: } else {
183: JDOMConfigurationProxy proxy = new JDOMConfigurationProxy(
184: dataNode, configurationClass);
185:
186: if (log.isTraceEnabled()) {
187: log
188: .trace("Instantiating new Configuration for document ["
189: + name
190: + "] with class of ["
191: + configurationClass.getName() + "]");
192: }
193: try {
194: config = (Configuration) Proxy.newProxyInstance(this
195: .getClass().getClassLoader(),
196: new Class[] { configurationClass }, proxy);
197: } catch (ClassCastException cce) {
198: throw new InvalidConfigurationException(
199: this .getClass(), name,
200: "ConfigurationInterface",
201: "Configuration class ["
202: + configurationClass.getName()
203: + "] was not assignable from ["
204: + Configuration.class.getName() + "]");
205: }
206: }
207: config.setConfigurationName(name);
208:
209: return config;
210: }
211:
212: /**
213: * <P>Stores the raw version of the provided <code>Configuration</code>
214: * object in the format that this format service implementation
215: * understands.</P>
216: *
217: * @param out The output stream to which the raw configuration
218: * data should be written
219: * @param configuration The Configuration object to be stored; may be any
220: * subclass of Configuration
221: * @throws ConfigurationFormatException When unable to write a
222: * configuration's raw format to the output stream
223: */
224: public void writeConfigurationStream(Configuration configuration,
225: OutputStream out) throws ConfigurationFormatException {
226:
227: Document dataNode = configuration.getDataStructure();
228:
229: ConfigurationDataFormatService dataFormat = new JDOMConfigurationFactory();
230:
231: dataFormat.writeConfigurationStreamToData(dataNode, out);
232: }
233:
234: /**
235: * Creates an indexed name for use within the format service. If
236: * parentName is not null, an absolute name is created, otherwise,
237: * parentName is ignored.
238: *
239: * @param parentName for use when creating absolute indexed names, can be
240: * null for creating local names
241: * @param name name of indexed attribute, cannot be null
242: * @param index cannot be negative
243: * @return String indexed name
244: * @since carbon 1.1
245: */
246: protected String constructIndexedName(String parentName,
247: String name, int index) {
248:
249: if (name == null) {
250: throw new InvalidParameterException(this .getClass(),
251: "Name cannot be null");
252: }
253:
254: if (index < 0) {
255: throw new InvalidParameterException(this .getClass(),
256: "Index cannot be negative");
257: }
258:
259: StringBuffer indexedName = new StringBuffer();
260: if (parentName != null) {
261: indexedName.append(parentName).append(Node.DELIMITER);
262: }
263: indexedName.append(name).append("[").append(index).append("]");
264: return indexedName.toString();
265: }
266:
267: /**
268: * Creates an indexed name for use within the format service. If
269: * parentName is not null, an absolute name is created, otherwise,
270: * parentName is ignored.
271: *
272: * @param parentName for use when creating absolute indexed names, can be
273: * null for creating local names
274: * @param name name of indexed attribute, cannot be null
275: * @param key they lookup key for the map attribute
276: * @return String indexed name
277: * @since carbon 2.0
278: */
279: protected String constructMapName(String parentName, String name,
280: String key) {
281:
282: if (name == null) {
283: throw new InvalidParameterException(this .getClass(),
284: "Name cannot be null");
285: }
286:
287: if (key == null) {
288: throw new InvalidParameterException(this .getClass(),
289: "Key must not be null.");
290: }
291:
292: StringBuffer indexedName = new StringBuffer();
293: if (parentName != null) {
294: indexedName.append(parentName).append(Node.DELIMITER);
295: }
296: indexedName.append(name).append("[").append(key).append("]");
297: return indexedName.toString();
298: }
299:
300: /**
301: * @see ConfigurationFormatService#getChildConfiguration
302: */
303: public Configuration getChildConfiguration(
304: Configuration parentConfig, String childName) {
305:
306: if (parentConfig == null) {
307: throw new InvalidParameterException(this .getClass(),
308: "parentConfig cannot be null");
309: }
310:
311: if (childName == null) {
312: throw new InvalidParameterException(this .getClass(),
313: "childName cannot be null");
314: }
315:
316: if (!Proxy.isProxyClass(parentConfig.getClass())) {
317: return null;
318: }
319:
320: AbstractConfigurationProxy configProxy = (AbstractConfigurationProxy) Proxy
321: .getInvocationHandler(parentConfig);
322:
323: if (childName.endsWith("]")) {
324: // the child name is indexed, parse childName to determine
325: // the actual name and the index
326:
327: int rightBracketIndex = childName.lastIndexOf("[");
328: String elementName = childName.substring(0,
329: rightBracketIndex);
330: String key = childName.substring(rightBracketIndex + 1,
331: childName.length() - 1);
332:
333: Class childType = configProxy.getChildType(elementName);
334: if (Map.class.isAssignableFrom(childType)) {
335: Map map = configProxy.getMap(elementName, configProxy
336: .getCollectionComponentType(elementName));
337: return (Configuration) map.get(key);
338: } else if (childType.isArray()) {
339: int index = Integer.parseInt(key);
340:
341: Object array = configProxy.getArray(elementName,
342: Configuration.class);
343:
344: return (Configuration) Array.get(array, index);
345: } else {
346: throw new InvalidParameterException(this .getClass(),
347: "Unknown child type.");
348: }
349: } else {
350: // lookup the attribute, pass in Configuration.class for the
351: // required type to ensure we get a Configuration back
352: return (Configuration) configProxy.lookupAttribute(
353: childName, Configuration.class);
354: }
355: }
356:
357: /**
358: * This implementation uses Introspection to determine the list of
359: * all attributes the parentConfig contains. It then uses the
360: * ConfigurationTypeService to determine which of those attributes
361: * are actually sub-configurations (i.e. complex types). If an attribute
362: * is a Map or Array, the actual data is retrieved from parentConfig
363: * and a name is returned for each element.
364: *
365: * @inherit
366: * @see ConfigurationFormatService#getChildConfigurationNames
367: */
368: public Set getChildConfigurationNames(Configuration parentConfig) {
369: if (parentConfig == null) {
370: throw new InvalidParameterException(this .getClass(),
371: "parentConfig cannot be null");
372: }
373:
374: final ConfigurationTypeService typeService = ConfigurationTypeServiceFactory
375: .getInstance();
376: Class configType = parentConfig.getConfigurationInterface();
377: Set childConfigurationNames = new HashSet();
378:
379: BeanInfo configBeanInfo = null;
380: try {
381: configBeanInfo = Introspector.getBeanInfo(configType);
382: } catch (IntrospectionException ie) {
383: log.info("Could not introspect confgiuration ["
384: + parentConfig.getConfigurationName()
385: + "], with config interface ["
386: + configType.getName()
387: + "], no children will be listed: cause: "
388: + ExceptionUtility.printStackTracesToString(ie));
389:
390: return childConfigurationNames;
391: }
392:
393: PropertyDescriptor[] properties = configBeanInfo
394: .getPropertyDescriptors();
395:
396: for (int i = 0; i < properties.length; i++) {
397: PropertyDescriptor property = properties[i];
398: Class returnType = property.getPropertyType();
399: String attributeName = StringUtil.capitalize(property
400: .getName());
401:
402: try {
403: Object attributeValue = property.getReadMethod()
404: .invoke(parentConfig, null);
405:
406: if (returnType == Map.class) {
407: Class contentType = getContentType(parentConfig
408: .getClass(), attributeName);
409:
410: if (contentType != null
411: && (typeService.isComplexType(contentType) || Configuration.class
412: .isAssignableFrom(contentType))) {
413:
414: Map attributeMap = (Map) attributeValue;
415: Set keySet = attributeMap.keySet();
416: for (Iterator keyIter = keySet.iterator(); keyIter
417: .hasNext();) {
418:
419: String key = (String) keyIter.next();
420: childConfigurationNames
421: .add(constructMapName(null,
422: attributeName, key));
423: }
424: }
425:
426: } else if (returnType.isArray()) {
427: Class contentType = attributeValue.getClass()
428: .getComponentType();
429:
430: if (typeService.isComplexType(contentType)
431: || Configuration.class
432: .isAssignableFrom(contentType)) {
433:
434: int arrayLength = Array
435: .getLength(attributeValue);
436: for (int count = 0; count < arrayLength; count++) {
437: childConfigurationNames
438: .add(constructIndexedName(null,
439: attributeName, count));
440: }
441: }
442:
443: } else if (attributeValue != null
444: && (typeService.isComplexType(returnType) || Configuration.class
445: .isAssignableFrom(returnType))) {
446:
447: childConfigurationNames.add(attributeName);
448: }
449:
450: } catch (IllegalAccessException iae) {
451: log.info("Could not read attribute ["
452: + attributeName
453: + "] from confgiuration ["
454: + parentConfig.getConfigurationName()
455: + "], attribute will not be listed as a "
456: + "child configuration: cause: "
457: + ExceptionUtility
458: .printStackTracesToString(iae));
459:
460: } catch (IllegalArgumentException iae) {
461: log.info("Could not read attribute ["
462: + attributeName
463: + "] from confgiuration ["
464: + parentConfig.getConfigurationName()
465: + "], attribute will not be listed as a "
466: + "child configuration: cause: "
467: + ExceptionUtility
468: .printStackTracesToString(iae));
469:
470: } catch (InvocationTargetException ite) {
471: log.info("Could not read attribute ["
472: + attributeName
473: + "] from confgiuration ["
474: + parentConfig.getConfigurationName()
475: + "], attribute will not be listed as a "
476: + "child configuration: cause: "
477: + ExceptionUtility.printStackTracesToString(ite
478: .getTargetException()));
479: }
480: }
481:
482: return childConfigurationNames;
483: }
484:
485: /**
486: * @inherit
487: * @see ConfigurationFormatService#alterChildConfiguration
488: */
489: public void alterChildConfiguration(Configuration parentConfig,
490: String childName, Configuration newConfig) {
491:
492: if (parentConfig == null) {
493: throw new InvalidParameterException(this .getClass(),
494: "parentConfig cannot be null");
495: }
496:
497: if (childName == null) {
498: throw new InvalidParameterException(this .getClass(),
499: "childName cannot be null");
500: }
501:
502: if (!Proxy.isProxyClass(parentConfig.getClass())) {
503: return;
504: }
505:
506: AbstractConfigurationProxy configProxy = (AbstractConfigurationProxy) Proxy
507: .getInvocationHandler(parentConfig);
508:
509: Class configurationInterface = Configuration.class;
510: if (newConfig != null) {
511: configurationInterface = newConfig
512: .getConfigurationInterface();
513: }
514:
515: if (childName.endsWith("]")) {
516: // indexed child name, parse out actual name and index
517: int rightBracketIndex = childName.lastIndexOf("[");
518: String elementName = childName.substring(0,
519: rightBracketIndex);
520: String key = childName.substring(rightBracketIndex + 1,
521: childName.length() - 1);
522:
523: Class childType = configProxy.getChildType(elementName);
524: if (Map.class.isAssignableFrom(childType)) {
525: Class childComponentType = configProxy
526: .getCollectionComponentType(elementName);
527: configProxy.setMapValue(elementName,
528: childComponentType, key, newConfig);
529:
530: } else if (childType.isArray()) {
531: int index = Integer.parseInt(key);
532:
533: // set the array value
534: configProxy.setArrayValue(elementName,
535: configurationInterface, index, newConfig);
536: } else {
537: throw new InvalidParameterException(this .getClass(),
538: "Unknown component type");
539: }
540:
541: } else {
542: // alter the existing value
543: configProxy.alterAttribute(childName,
544: configurationInterface, newConfig);
545: }
546: }
547:
548: /**
549: * Gets the type of class contained within the given map.
550: * This assumes that all elements in the map are the same
551: * class.
552: *
553: * @param contentMap map to check the content class
554: * @return the type of class contained within the map.
555: */
556: private Class getContentType(Class configurationType,
557: String attrName) {
558: Class componentType = null;
559:
560: try {
561: Method readMethod = configurationType.getMethod("get"
562: + attrName, new Class[] { String.class });
563:
564: componentType = readMethod.getReturnType();
565: } catch (NoSuchMethodException e) {
566: // read method does not exist, so leave componentType as null
567: }
568:
569: return componentType;
570: }
571: }
|