001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */package org.apache.cxf.aegis.type.basic;
019:
020: import java.beans.PropertyDescriptor;
021: import java.lang.reflect.Constructor;
022: import java.lang.reflect.InvocationTargetException;
023: import java.lang.reflect.Method;
024: import java.lang.reflect.Modifier;
025: import java.lang.reflect.Proxy;
026: import java.util.HashSet;
027: import java.util.Iterator;
028: import java.util.Set;
029:
030: import javax.xml.namespace.QName;
031:
032: import org.apache.cxf.aegis.Context;
033: import org.apache.cxf.aegis.DatabindingException;
034: import org.apache.cxf.aegis.type.Type;
035: import org.apache.cxf.aegis.type.TypeMapping;
036: import org.apache.cxf.aegis.type.TypeUtil;
037: import org.apache.cxf.aegis.util.NamespaceHelper;
038: import org.apache.cxf.aegis.util.XmlConstants;
039: import org.apache.cxf.aegis.xml.MessageReader;
040: import org.apache.cxf.aegis.xml.MessageWriter;
041: import org.apache.cxf.common.classloader.ClassLoaderUtils;
042: import org.apache.cxf.interceptor.Fault;
043: import org.jdom.Attribute;
044: import org.jdom.Element;
045:
046: /**
047: * Serializes JavaBeans.
048: *
049: * @author <a href="mailto:dan@envoisolutions.com">Dan Diephouse</a>
050: * @author <a href="mailto:jack.xu.hong@gmail.com">Jack Hong</a>
051: */
052: public class BeanType extends Type {
053: private BeanTypeInfo info;
054:
055: private boolean isInterface;
056:
057: private boolean isException;
058:
059: public BeanType() {
060: }
061:
062: public BeanType(BeanTypeInfo info) {
063: this .info = info;
064: this .typeClass = info.getTypeClass();
065: initTypeClass();
066: }
067:
068: private void initTypeClass() {
069: this .isInterface = typeClass.isInterface();
070: isException = Exception.class.isAssignableFrom(typeClass);
071: }
072:
073: /*
074: * (non-Javadoc)
075: *
076: * @see org.codehaus.xfire.aegis.type.Type#readObject(org.codehaus.xfire.aegis.MessageReader,
077: * org.codehaus.xfire.MessageContext)
078: */
079: @Override
080: public Object readObject(MessageReader reader, Context context)
081: throws DatabindingException {
082: BeanTypeInfo inf = getTypeInfo();
083:
084: try {
085: Class clazz = getTypeClass();
086: Object object = null;
087: InterfaceInvocationHandler delegate = null;
088: boolean isProxy = false;
089:
090: if (isInterface) {
091: String impl = (String) context.get(clazz.getName()
092: + ".implementation");
093:
094: if (impl == null) {
095: delegate = new InterfaceInvocationHandler();
096: object = Proxy.newProxyInstance(this .getClass()
097: .getClassLoader(), new Class[] { clazz },
098: delegate);
099: isProxy = true;
100: } else {
101: try {
102: clazz = ClassLoaderUtils.loadClass(impl,
103: getClass());
104: object = clazz.newInstance();
105: } catch (ClassNotFoundException e) {
106: throw new DatabindingException(
107: "Could not find implementation class "
108: + impl + " for class "
109: + clazz.getName());
110: }
111: }
112: } else if (isException) {
113: object = createFromFault(context);
114: } else {
115: object = clazz.newInstance();
116: }
117:
118: // Read attributes
119: while (reader.hasMoreAttributeReaders()) {
120: MessageReader childReader = reader
121: .getNextAttributeReader();
122: QName name = childReader.getName();
123:
124: Type type = inf.getType(name);
125:
126: if (type != null) {
127: Object writeObj = type.readObject(childReader,
128: context);
129: if (isProxy) {
130: delegate.writeProperty(name.getLocalPart(),
131: writeObj);
132: } else {
133: writeProperty(name, object, writeObj, clazz,
134: inf);
135: }
136: }
137: }
138:
139: // Read child elements
140: while (reader.hasMoreElementReaders()) {
141: MessageReader childReader = reader
142: .getNextElementReader();
143: QName name = childReader.getName();
144:
145: BeanType parent = getBeanTypeWithProperty(name);
146: Type defaultType = null;
147: if (parent != null) {
148: info = parent.getTypeInfo();
149: defaultType = info.getType(name);
150: }
151:
152: Type type = TypeUtil.getReadType(childReader
153: .getXMLStreamReader(), context, defaultType);
154:
155: // If the xsi:type lookup didn't work or there was none, use the
156: // normal Type.
157: if (type == null) {
158: parent = getBeanTypeWithProperty(name);
159: if (parent != null) {
160: inf = parent.getTypeInfo();
161: type = inf.getType(name);
162: } else {
163: type = null;
164: }
165: }
166:
167: if (type != null) {
168: if (!childReader.isXsiNil()) {
169: Object writeObj = type.readObject(childReader,
170: context);
171:
172: if (isProxy) {
173: delegate.writeProperty(name.getLocalPart(),
174: writeObj);
175: } else {
176: writeProperty(name, object, writeObj,
177: clazz, inf);
178: }
179: } else {
180: if (!inf.isNillable(name)) {
181: throw new DatabindingException(name
182: .getLocalPart()
183: + " is nil, but not nillable.");
184:
185: }
186: childReader.readToEnd();
187: }
188: } else {
189: childReader.readToEnd();
190: }
191: }
192:
193: return object;
194: } catch (IllegalAccessException e) {
195: throw new DatabindingException("Illegal access. "
196: + e.getMessage(), e);
197: } catch (InstantiationException e) {
198: throw new DatabindingException(
199: "Couldn't instantiate class. " + e.getMessage(), e);
200: } catch (SecurityException e) {
201: throw new DatabindingException("Illegal access. "
202: + e.getMessage(), e);
203: } catch (IllegalArgumentException e) {
204: throw new DatabindingException("Illegal argument. "
205: + e.getMessage(), e);
206: } catch (InvocationTargetException e) {
207: throw new DatabindingException("Could not create class: "
208: + e.getMessage(), e);
209: }
210: }
211:
212: /**
213: * If the class is an exception, this will try and instantiate it with
214: * information from the XFireFault (if it exists).
215: */
216: protected Object createFromFault(Context context)
217: throws SecurityException, InstantiationException,
218: IllegalAccessException, IllegalArgumentException,
219: InvocationTargetException {
220: Class clazz = getTypeClass();
221: Constructor ctr;
222: Object o;
223:
224: Fault fault = context.getFault();
225:
226: try {
227: ctr = clazz.getConstructor(new Class[] { String.class,
228: Throwable.class });
229: o = ctr.newInstance(new Object[] { fault.getMessage(),
230: fault });
231: } catch (NoSuchMethodException e) {
232: try {
233: ctr = clazz.getConstructor(new Class[] { String.class,
234: Exception.class });
235: o = ctr.newInstance(new Object[] { fault.getMessage(),
236: fault });
237: } catch (NoSuchMethodException e1) {
238: try {
239: ctr = clazz
240: .getConstructor(new Class[] { String.class });
241: o = ctr.newInstance(new Object[] { fault
242: .getMessage() });
243: } catch (NoSuchMethodException e2) {
244: return clazz.newInstance();
245: }
246: }
247: }
248:
249: return o;
250: }
251:
252: /**
253: * Write the specified property to a field.
254: */
255: protected void writeProperty(QName name, Object object,
256: Object property, Class impl, BeanTypeInfo inf)
257: throws DatabindingException {
258: try {
259: PropertyDescriptor desc = inf
260: .getPropertyDescriptorFromMappedName(name);
261:
262: Method m = desc.getWriteMethod();
263:
264: if (m == null) {
265: if (getTypeClass().isInterface()) {
266: m = getWriteMethodFromImplClass(impl, desc);
267: }
268:
269: if (m == null) {
270: throw new DatabindingException(
271: "No write method for property " + name
272: + " in " + object.getClass());
273: }
274: }
275:
276: Class propertyType = desc.getPropertyType();
277: if ((property == null && !propertyType.isPrimitive())
278: || (property != null)) {
279: m.invoke(object, new Object[] { property });
280: }
281: } catch (Exception e) {
282: if (e instanceof DatabindingException) {
283: throw (DatabindingException) e;
284: }
285:
286: throw new DatabindingException("Couldn't set property "
287: + name + " on " + object + ". " + e.getMessage(), e);
288: }
289: }
290:
291: /**
292: * This is a hack to get the write method from the implementation class for
293: * an interface.
294: */
295: private Method getWriteMethodFromImplClass(Class<?> impl,
296: PropertyDescriptor pd) throws Exception {
297: String name = pd.getName();
298: name = "set" + name.substring(0, 1).toUpperCase()
299: + name.substring(1);
300:
301: return impl.getMethod(name,
302: new Class[] { pd.getPropertyType() });
303: }
304:
305: /**
306: * @see org.apache.cxf.aegis.type.Type#writeObject(Object,
307: * org.apache.cxf.aegis.xml.MessageWriter,
308: * org.apache.cxf.aegis.Context)
309: */
310: @Override
311: public void writeObject(Object object, MessageWriter writer,
312: Context context) throws DatabindingException {
313: if (object == null) {
314: return;
315: }
316:
317: BeanTypeInfo inf = getTypeInfo();
318:
319: if (object.getClass() == getTypeClass()
320: && context.isWriteXsiTypes()) {
321: writer.writeXsiType(getSchemaType());
322: }
323:
324: /*
325: * TODO: Replace this method with one split into two pieces so that we
326: * can front-load the attributes and traverse down the list of super
327: * classes.
328: */
329: for (Iterator itr = inf.getAttributes(); itr.hasNext();) {
330: QName name = (QName) itr.next();
331:
332: Object value = readProperty(object, name);
333: if (value != null) {
334: Type type = getType(inf, name);
335:
336: if (type == null) {
337: throw new DatabindingException(
338: "Couldn't find type for "
339: + value.getClass()
340: + " for property " + name);
341: }
342:
343: MessageWriter cwriter = writer.getAttributeWriter(name);
344:
345: type.writeObject(value, cwriter, context);
346:
347: cwriter.close();
348: }
349: }
350:
351: for (Iterator itr = inf.getElements(); itr.hasNext();) {
352: QName name = (QName) itr.next();
353:
354: if (inf.isExtension()
355: && inf.getPropertyDescriptorFromMappedName(name)
356: .getReadMethod().getDeclaringClass() != inf
357: .getTypeClass()) {
358: continue;
359: }
360: Object value = readProperty(object, name);
361:
362: Type defaultType = getType(inf, name);
363: Type type = TypeUtil.getWriteType(context, value,
364: defaultType);
365: MessageWriter cwriter;
366:
367: // Write the value if it is not null.
368: if (value != null) {
369: cwriter = getWriter(writer, name, type);
370:
371: if (type == null) {
372: throw new DatabindingException(
373: "Couldn't find type for "
374: + value.getClass()
375: + " for property " + name);
376: }
377:
378: type.writeObject(value, cwriter, context);
379:
380: cwriter.close();
381: } else if (inf.isNillable(name)) {
382: cwriter = getWriter(writer, name, type);
383:
384: // Write the xsi:nil if it is null.
385: cwriter.writeXsiNil();
386:
387: cwriter.close();
388: }
389: }
390: if (inf.isExtension()) {
391: Type t = getSuperType();
392: if (t != null) {
393: t.writeObject(object, writer, context);
394: }
395: }
396: }
397:
398: private MessageWriter getWriter(MessageWriter writer, QName name,
399: Type type) {
400: MessageWriter cwriter;
401: if (type.isAbstract()) {
402: cwriter = writer.getElementWriter(name);
403: } else {
404: cwriter = writer.getElementWriter(name);
405: }
406: return cwriter;
407: }
408:
409: protected Object readProperty(Object object, QName name) {
410: try {
411: PropertyDescriptor desc = getTypeInfo()
412: .getPropertyDescriptorFromMappedName(name);
413:
414: Method m = desc.getReadMethod();
415:
416: if (m == null) {
417: throw new DatabindingException(
418: "No read method for property " + name
419: + " in class "
420: + object.getClass().getName());
421: }
422:
423: return m.invoke(object, new Object[0]);
424: } catch (Exception e) {
425: throw new DatabindingException("Couldn't get property "
426: + name + " from bean " + object, e);
427: }
428: }
429:
430: /**
431: * @see org.apache.cxf.aegis.type.Type#writeSchema(org.jdom.Element)
432: */
433: @Override
434: public void writeSchema(Element root) {
435: BeanTypeInfo inf = getTypeInfo();
436: Element complex = new Element("complexType",
437: XmlConstants.XSD_PREFIX, XmlConstants.XSD);
438: complex.setAttribute(new Attribute("name", getSchemaType()
439: .getLocalPart()));
440: root.addContent(complex);
441:
442: Type sooperType = getSuperType();
443:
444: /*
445: * See Java Virtual Machine specification:
446: * http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#75734
447: */
448: if (((inf.getTypeClass().getModifiers() & Modifier.ABSTRACT) != 0)
449: && !inf.getTypeClass().isInterface()) {
450: complex.setAttribute(new Attribute("abstract", "true"));
451: }
452:
453: if (inf.isExtension() && sooperType != null) {
454: Element complexContent = new Element("complexContent",
455: XmlConstants.XSD_PREFIX, XmlConstants.XSD);
456: complex.addContent(complexContent);
457: complex = complexContent;
458: }
459:
460: /*
461: * Decide if we're going to extend another type. If we are going to
462: * defer, then make sure that we extend the type for our superclass.
463: */
464: boolean isExtension = inf.isExtension();
465:
466: Element dummy = complex;
467:
468: if (isExtension && sooperType != null) {
469:
470: Element extension = new Element("extension",
471: XmlConstants.XSD_PREFIX, XmlConstants.XSD);
472: complex.addContent(extension);
473: QName baseType = sooperType.getSchemaType();
474: extension.setAttribute(new Attribute("base",
475: getNameWithPrefix2(root,
476: baseType.getNamespaceURI(), baseType
477: .getLocalPart())));
478:
479: dummy = extension;
480: }
481:
482: Element seq = null;
483:
484: // Write out schema for elements
485: for (Iterator itr = inf.getElements(); itr.hasNext();) {
486:
487: QName name = (QName) itr.next();
488:
489: if (isExtension) {
490: PropertyDescriptor pd = inf
491: .getPropertyDescriptorFromMappedName(name);
492:
493: assert pd.getReadMethod() != null
494: && pd.getWriteMethod() != null;
495: if (pd.getReadMethod().getDeclaringClass() != inf
496: .getTypeClass()) {
497: continue;
498: }
499: }
500:
501: if (seq == null) {
502: seq = new Element("sequence", XmlConstants.XSD_PREFIX,
503: XmlConstants.XSD);
504: dummy.addContent(seq);
505: }
506:
507: Element element = new Element("element",
508: XmlConstants.XSD_PREFIX, XmlConstants.XSD);
509: seq.addContent(element);
510:
511: Type type = getType(inf, name);
512:
513: String nameNS = name.getNamespaceURI();
514: String nameWithPrefix = getNameWithPrefix(root, nameNS,
515: name.getLocalPart());
516:
517: String prefix = NamespaceHelper.getUniquePrefix(root, type
518: .getSchemaType().getNamespaceURI());
519:
520: writeTypeReference(name, nameWithPrefix, element, type,
521: prefix);
522: }
523:
524: /**
525: * if future proof then add <xsd:any/> element
526: */
527: if (inf.isExtensibleElements()) {
528: if (seq == null) {
529: seq = new Element("sequence", XmlConstants.XSD_PREFIX,
530: XmlConstants.XSD);
531: dummy.addContent(seq);
532: }
533: seq.addContent(createAnyElement());
534: }
535:
536: // Write out schema for attributes
537: for (Iterator itr = inf.getAttributes(); itr.hasNext();) {
538: QName name = (QName) itr.next();
539:
540: Element element = new Element("attribute",
541: XmlConstants.XSD_PREFIX, XmlConstants.XSD);
542: dummy.addContent(element);
543:
544: Type type = getType(inf, name);
545:
546: String nameNS = name.getNamespaceURI();
547: String nameWithPrefix = getNameWithPrefix(root, nameNS,
548: name.getLocalPart());
549:
550: String prefix = NamespaceHelper.getUniquePrefix(root, type
551: .getSchemaType().getNamespaceURI());
552: element.setAttribute(new Attribute("name", nameWithPrefix));
553: element.setAttribute(new Attribute("type", prefix + ':'
554: + type.getSchemaType().getLocalPart()));
555: }
556:
557: /**
558: * If extensible attributes then add <xsd:anyAttribute/>
559: */
560: if (inf.isExtensibleAttributes()) {
561: dummy.addContent(createAnyAttribute());
562: }
563: }
564:
565: private String getNameWithPrefix(Element root, String nameNS,
566: String localName) {
567: if (!nameNS.equals(getSchemaType().getNamespaceURI())) {
568: String prefix = NamespaceHelper.getUniquePrefix(
569: (Element) root.getParent(), nameNS);
570:
571: if (prefix == null || prefix.length() == 0) {
572: prefix = NamespaceHelper.getUniquePrefix(root, nameNS);
573: }
574:
575: return prefix + ":" + localName;
576: }
577: return localName;
578: }
579:
580: private String getNameWithPrefix2(Element root, String nameNS,
581: String localName) {
582: String prefix = NamespaceHelper.getUniquePrefix(root, nameNS);
583:
584: if (prefix == null || prefix.length() == 0) {
585: prefix = NamespaceHelper.getUniquePrefix(root, nameNS);
586: }
587:
588: return prefix + ":" + localName;
589: }
590:
591: private Type getType(BeanTypeInfo inf, QName name) {
592: Type type = inf.getType(name);
593:
594: if (type == null) {
595: throw new NullPointerException("Couldn't find type for"
596: + name + " in class " + getTypeClass().getName());
597: }
598:
599: return type;
600: }
601:
602: private void writeTypeReference(QName name, String nameWithPrefix,
603: Element element, Type type, String prefix) {
604: if (type.isAbstract()) {
605: element.setAttribute(new Attribute("name", nameWithPrefix));
606: element.setAttribute(new Attribute("type", prefix + ':'
607: + type.getSchemaType().getLocalPart()));
608:
609: int minOccurs = getTypeInfo().getMinOccurs(name);
610: if (minOccurs != 1) {
611: element.setAttribute(new Attribute("minOccurs", Integer
612: .valueOf(minOccurs).toString()));
613: }
614:
615: if (getTypeInfo().isNillable(name)) {
616: element.setAttribute(new Attribute("nillable", "true"));
617: }
618: } else {
619: element.setAttribute(new Attribute("ref", prefix + ':'
620: + type.getSchemaType().getLocalPart()));
621: }
622: }
623:
624: @Override
625: public void setTypeClass(Class typeClass) {
626: super .setTypeClass(typeClass);
627:
628: initTypeClass();
629: }
630:
631: /**
632: * We need to write a complex type schema for Beans, so return true.
633: *
634: * @see org.apache.cxf.aegis.type.Type#isComplex()
635: */
636: @Override
637: public boolean isComplex() {
638: return true;
639: }
640:
641: @Override
642: public Set<Type> getDependencies() {
643: Set<Type> deps = new HashSet<Type>();
644:
645: BeanTypeInfo inf = getTypeInfo();
646:
647: for (Iterator itr = inf.getAttributes(); itr.hasNext();) {
648: QName name = (QName) itr.next();
649: deps.add(inf.getType(name));
650: }
651:
652: for (Iterator itr = inf.getElements(); itr.hasNext();) {
653: QName name = (QName) itr.next();
654: if (inf.isExtension()
655: && inf.getPropertyDescriptorFromMappedName(name)
656: .getReadMethod().getDeclaringClass() != inf
657: .getTypeClass()) {
658: continue;
659: }
660: deps.add(inf.getType(name));
661: }
662:
663: /*
664: * Automagically add chain of superclasses *if* this is an an extension.
665: */
666: if (inf.isExtension()) {
667: Type sooperType = getSuperType();
668: if (sooperType != null) {
669: deps.add(sooperType);
670: }
671: }
672:
673: return deps;
674: }
675:
676: private BeanType getBeanTypeWithProperty(QName name) {
677: BeanType sooper = this ;
678: Type type = null;
679:
680: while (type == null && sooper != null) {
681: type = sooper.getTypeInfo().getType(name);
682:
683: if (type == null) {
684: sooper = sooper.getSuperType();
685: }
686: }
687:
688: return sooper;
689: }
690:
691: private BeanType getSuperType() {
692: BeanTypeInfo inf = getTypeInfo();
693: Class c = inf.getTypeClass().getSuperclass();
694: /*
695: * Don't dig any deeper than Object or Exception
696: */
697: if (c != null && c != Object.class && c != Exception.class
698: && c != RuntimeException.class) {
699: TypeMapping tm = inf.getTypeMapping();
700: BeanType super Type = (BeanType) tm.getType(c);
701: if (super Type == null) {
702: super Type = (BeanType) getTypeMapping()
703: .getTypeCreator().createType(c);
704: Class cParent = c.getSuperclass();
705: if (cParent != null && cParent != Object.class) {
706: super Type.getTypeInfo().setExtension(true);
707: }
708: tm.register(super Type);
709: }
710: return super Type;
711: } else {
712: return null;
713: }
714: }
715:
716: public BeanTypeInfo getTypeInfo() {
717: if (info == null) {
718: info = createTypeInfo();
719: }
720:
721: info.initialize();
722:
723: return info;
724: }
725:
726: public BeanTypeInfo createTypeInfo() {
727: BeanTypeInfo inf = new BeanTypeInfo(getTypeClass(),
728: getSchemaType().getNamespaceURI());
729:
730: inf.setTypeMapping(getTypeMapping());
731:
732: return inf;
733: }
734:
735: /**
736: * Create an element to represent any future elements that might get added
737: * to the schema <xsd:any minOccurs="0" maxOccurs="unbounded"/>
738: *
739: * @return
740: */
741: private Element createAnyElement() {
742: Element result = new Element("any", XmlConstants.XSD_PREFIX,
743: XmlConstants.XSD);
744: result.setAttribute(new Attribute("minOccurs", "0"));
745: result.setAttribute(new Attribute("maxOccurs", "unbounded"));
746: return result;
747: }
748:
749: @Override
750: public String toString() {
751: StringBuffer sb = new StringBuffer();
752: sb.append(getClass().getName());
753: sb.append(": [class=");
754: Class c = getTypeClass();
755: sb.append((c == null) ? "<null>" : c.getName());
756: sb.append(",\nQName=");
757: QName q = getSchemaType();
758: sb.append((q == null) ? "<null>" : q.toString());
759: sb.append(",\ninfo=");
760: sb.append(getTypeInfo().toString());
761: sb.append("]");
762: return sb.toString();
763: }
764:
765: /**
766: * Create an element to represent any future attributes that might get added
767: * to the schema <xsd:anyAttribute/>
768: *
769: * @return
770: */
771: private Element createAnyAttribute() {
772: return new Element("anyAttribute", XmlConstants.XSD_PREFIX,
773: XmlConstants.XSD);
774: }
775:
776: }
|