1 /*
2 * JBoss, the OpenSource J2EE webOS
3 *
4 * Distributable under LGPL license.
5 * See terms of license at gnu.org.
6 */
7
8 // $Id: EntityBeanDeserializer.java,v 1.2.4.2 2002/11/20 04:00:57 starksm Exp $
9
10 package org.jboss.net.axis.server;
11
12 import org.jboss.net.axis.ParameterizableDeserializer;
13
14 import org.xml.sax.Attributes;
15 import org.xml.sax.SAXException;
16
17 import javax.xml.namespace.QName;
18
19 import org.apache.axis.encoding.DeserializerImpl;
20 import org.apache.axis.encoding.DeserializationContext;
21 import org.apache.axis.encoding.Deserializer;
22 import org.apache.axis.encoding.ser.SimpleDeserializer;
23 import org.apache.axis.encoding.Target;
24 import org.apache.axis.encoding.TypeMapping;
25 import org.apache.axis.utils.JavaUtils;
26 import org.apache.axis.utils.Messages;
27 import org.apache.axis.Constants;
28 import org.apache.axis.description.TypeDesc;
29 import org.apache.axis.message.SOAPHandler;
30
31 import java.lang.reflect.Method;
32 import java.lang.reflect.InvocationTargetException;
33
34 import javax.naming.InitialContext;
35 import javax.ejb.EJBHome;
36 import javax.naming.NamingException;
37
38 import java.beans.PropertyDescriptor;
39 import java.beans.IntrospectionException;
40 import java.beans.Introspector;
41
42 import java.util.Map;
43 import java.util.List;
44 import java.util.Collection;
45 import java.util.Iterator;
46 import java.util.StringTokenizer;
47
48 /**
49 * Server-side deserializer hitting an existing entity bean. Derived
50 * from the axis BeanDeserializer. Currently relies on some
51 * silly conventions that must be configurable in the deployment
52 * descriptor.
53 * @author jung
54 * @created 21.03.2002
55 * @version $Revision: 1.2.4.2 $
56 */
57
58 public class EntityBeanDeserializer
59 extends DeserializerImpl
60 implements ParameterizableDeserializer {
61
62 //
63 // Attributes
64 //
65
66 protected Map options;
67
68 protected Object home;
69 protected Method findMethod;
70 protected List findElements = new java.util.ArrayList(1);
71 protected Object[] findObjects;
72 protected TypeDesc typeDesc;
73 protected QName xmlType;
74 protected Class javaType;
75 protected Map propertyMap = new java.util.HashMap(4);
76 protected int collectionIndex = -1;
77 protected Collection fieldSetters = new java.util.ArrayList(4);
78 protected boolean initialized = false;
79
80 /**
81 * Construct a new BeanSerializer
82 * @param remoteType remote interface of the entity bean
83 * @param xmlType fully-qualified xml tag-name of the corresponding xml structure
84 */
85
86 public EntityBeanDeserializer(Class remoteType, QName xmlType)
87 throws Exception {
88 // first the default constructor
89 this.xmlType = xmlType;
90 this.javaType = remoteType;
91 }
92
93 /** returns an option string with a default */
94 protected String getStringOption(String key, String def) {
95 String value = (String) options.get(key);
96 if (value == null) {
97 value = def;
98 }
99 return value;
100 }
101
102 /**
103 * initialize the deserializer
104 */
105
106 protected void initialize() throws SAXException {
107 if (!initialized) {
108 initialized = true;
109
110 try {
111 //
112 // Extract home from jndiName
113 //
114 this.home =
115 new InitialContext().lookup(
116 getStringOption("JndiName", javaType.getName() + "Home"));
117
118 //
119 // Extract find method from name and sig
120 //
121
122 String findMethodName = getStringOption("FindMethodName", "findByPrimaryKey");
123 String findMethodSignatureString =
124 getStringOption("FindMethodSignature", "java.lang.String");
125 List findMethodSignatureClasses = new java.util.ArrayList(1);
126 StringTokenizer tokenizer = new StringTokenizer(findMethodSignatureString, ";");
127 while (tokenizer.hasMoreTokens()) {
128 findMethodSignatureClasses.add(
129 Thread.currentThread().getContextClassLoader().loadClass(
130 tokenizer.nextToken()));
131 }
132 this.findMethod =
133 home.getClass().getMethod(
134 findMethodName,
135 (Class[]) findMethodSignatureClasses.toArray(
136 new Class[findMethodSignatureClasses.size()]));
137
138 //
139 // Do some reasonable preprocessing
140 //
141
142 // Get a list of the bean properties
143 BeanPropertyDescriptor[] pd = getPd(javaType);
144 // loop through properties and grab the names for later
145 for (int i = 0; i < pd.length; i++) {
146 BeanPropertyDescriptor descriptor = pd[i];
147 propertyMap.put(descriptor.getName(), descriptor);
148 propertyMap.put(JavaUtils.xmlNameToJava(descriptor.getName()), descriptor);
149 }
150 typeDesc = TypeDesc.getTypeDescForClass(javaType);
151
152 //
153 // Next prepare the elements we need to call the finder
154 //
155
156 String findMethodElements = getStringOption("FindMethodElements", "name");
157 tokenizer = new StringTokenizer(findMethodElements, ";");
158 while (tokenizer.hasMoreElements()) {
159 if (typeDesc != null) {
160 this.findElements.add(typeDesc.getAttributeNameForField(tokenizer.nextToken()));
161 } else {
162 this.findElements.add(new QName("", tokenizer.nextToken()));
163 }
164 }
165
166 this.findObjects = new Object[findElements.size()];
167 } catch (NamingException e) {
168 throw new SAXException("Could not lookup home.", e);
169 } catch (ClassNotFoundException e) {
170 throw new SAXException("Could not find signature class.", e);
171 } catch (NoSuchMethodException e) {
172 throw new SAXException("Could not find finder method.", e);
173 }
174
175 }
176 }
177
178 public void setOptions(Map options) {
179 this.options = options;
180 }
181
182 public Map getOptions() {
183 return options;
184 }
185
186 /**
187 * Deserializer interface called on each child element encountered in
188 * the XML stream.
189 * @param namespace is the namespace of the child element
190 * @param localName is the local name of the child element
191 * @param prefix is the prefix used on the name of the child element
192 * @param attributes are the attributes of the child element
193 * @param context is the deserialization context.
194 * @return is a Deserializer to use to deserialize a child (must be
195 * a derived class of SOAPHandler) or null if no deserialization should
196 * be performed.
197 */
198 public SOAPHandler onStartChild(
199 String namespace,
200 String localName,
201 String prefix,
202 Attributes attributes,
203 DeserializationContext context)
204 throws SAXException {
205 BeanPropertyDescriptor propDesc = null;
206
207 if (typeDesc != null) {
208 QName elemQName = new QName(namespace, localName);
209 String fieldName = typeDesc.getFieldNameForElement(elemQName,false);
210 propDesc = (BeanPropertyDescriptor) propertyMap.get(fieldName);
211 }
212
213 if (propDesc == null) {
214 // look for a field by this name.
215 propDesc = (BeanPropertyDescriptor) propertyMap.get(localName);
216 }
217 if (propDesc == null) {
218 // look for a field by the "adjusted" name.
219 propDesc =
220 (BeanPropertyDescriptor) propertyMap.get(JavaUtils.xmlNameToJava(localName));
221 }
222
223 if (propDesc == null) {
224 // No such field
225 throw new SAXException(
226 Messages.getMessage("badElem00", javaType.getName(), localName));
227 }
228
229 // Determine the QName for this child element.
230 // Look at the type attribute specified. If this fails,
231 // use the javaType of the property to get the type qname.
232 QName qn = context.getTypeFromAttributes(namespace, localName, attributes);
233
234 // get the deserializer
235 Deserializer dSer = context.getDeserializerForType(qn);
236
237 // If no deserializer, use the base DeserializerImpl.
238 // There may not be enough information yet to choose the
239 // specific deserializer.
240 if (dSer == null) {
241 dSer = new DeserializerImpl();
242 // determine a default type for this child element
243 TypeMapping tm = context.getTypeMapping();
244 Class type = propDesc.getType();
245 dSer.setDefaultType(tm.getTypeQName(type));
246 }
247
248 QName elementQName = new QName(namespace, localName);
249 if (findElements.contains(elementQName)) {
250 dSer.registerValueTarget(
251 new FindPropertyTarget(findElements.indexOf(elementQName)));
252 } else if (propDesc.getWriteMethod().getParameterTypes().length == 1) {
253 // Success! Register the target and deserializer.
254 collectionIndex = -1;
255 dSer.registerValueTarget(new BeanPropertyTarget(propDesc));
256 } else {
257 // Success! This is a collection of properties so use the index
258 collectionIndex++;
259 dSer.registerValueTarget(new BeanPropertyTarget(propDesc, collectionIndex));
260 }
261 return (SOAPHandler) dSer;
262 }
263
264 /**
265 * Set the bean properties that correspond to element attributes.
266 *
267 * This method is invoked after startElement when the element requires
268 * deserialization (i.e. the element is not an href and the value is not nil.)
269 * @param namespace is the namespace of the element
270 * @param localName is the name of the element
271 * @param qName is the prefixed qName of the element
272 * @param attributes are the attributes on the element...used to get the type
273 * @param context is the DeserializationContext
274 */
275 public void onStartElement(
276 String namespace,
277 String localName,
278 String qName,
279 Attributes attributes,
280 DeserializationContext context)
281 throws SAXException {
282
283 initialize();
284
285 if (typeDesc == null)
286 return;
287
288 // loop through the attributes and set bean properties that
289 // correspond to attributes
290 for (int i = 0; i < attributes.getLength(); i++) {
291 QName attrQName = new QName(attributes.getURI(i), attributes.getLocalName(i));
292 String fieldName = typeDesc.getFieldNameForAttribute(attrQName);
293 if (fieldName == null)
294 continue;
295
296 String attrName = attributes.getLocalName(i);
297
298 // look for the attribute property
299 BeanPropertyDescriptor bpd =
300 (BeanPropertyDescriptor) propertyMap.get(fieldName);
301 if (bpd != null) {
302 if (bpd.getWriteMethod() == null)
303 continue;
304
305 // determine the QName for this child element
306 TypeMapping tm = context.getTypeMapping();
307 Class type = bpd.getType();
308 QName qn = tm.getTypeQName(type);
309 if (qn == null)
310 throw new SAXException(Messages.getMessage("unregistered00", type.toString()));
311
312 // get the deserializer
313 Deserializer dSer = context.getDeserializerForType(qn);
314 if (dSer == null)
315 throw new SAXException(Messages.getMessage("noDeser00", type.toString()));
316 if (!(dSer instanceof SimpleDeserializer))
317 throw new SAXException(
318 Messages.getMessage("AttrNotSimpleType00", bpd.getName(), type.toString()));
319
320 if (findElements.contains(attrQName)) {
321 dSer.registerValueTarget(
322 new FindPropertyTarget(findElements.indexOf(attrQName)));
323 } else if (bpd.getWriteMethod().getParameterTypes().length == 1) {
324 // Success! Create an object from the string and set
325 // it in the bean
326 try {
327 Object val = ((SimpleDeserializer) dSer).makeValue(attributes.getValue(i));
328 bpd.getWriteMethod().invoke(value, new Object[] { val });
329 } catch (Exception e) {
330 throw new SAXException(e);
331 }
332 }
333
334 } // if
335 } // attribute loop
336 }
337
338 public void onEndElement(
339 String namespace,
340 String localName,
341 DeserializationContext context)
342 throws SAXException {
343 try {
344 value = findMethod.invoke(home, findObjects);
345 Iterator allSetters = fieldSetters.iterator();
346 while (allSetters.hasNext()) {
347 ((BeanPropertyTarget) allSetters.next()).setReal(value);
348 }
349 fieldSetters = null;
350 } catch (InvocationTargetException e) {
351 throw new SAXException("Encountered exception " + e.getTargetException());
352 } catch (IllegalAccessException e) {
353 throw new SAXException("Encountered exception " + e);
354 }
355 super.onEndElement(namespace, localName, context);
356 }
357
358 public class FindPropertyTarget implements Target {
359 int position;
360
361 public FindPropertyTarget(int index) {
362 this.position = index;
363 }
364
365 public void set(Object value) throws SAXException {
366 findObjects[position] = value;
367 }
368 }
369
370 /**
371 * Class which knows how to update a bean property
372 */
373 public class BeanPropertyTarget implements Target {
374 private BeanPropertyDescriptor pd;
375 private int index = -1;
376 Object value;
377
378 /**
379 * This constructor is used for a normal property.
380 * @param Object is the bean class
381 * @param pd is the property
382 **/
383 public BeanPropertyTarget(BeanPropertyDescriptor pd) {
384 this.pd = pd;
385 this.index = -1; // disable indexing
386 }
387
388 /**
389 * This constructor is used for an indexed property.
390 * @param Object is the bean class
391 * @param pd is the property
392 * @param i is the index
393 **/
394 public BeanPropertyTarget(BeanPropertyDescriptor pd, int i) {
395 this.pd = pd;
396 this.index = i;
397 }
398
399 public void set(Object value) throws SAXException {
400 this.value = value;
401 if (fieldSetters != null) {
402 fieldSetters.add(this);
403 } else {
404 setReal(EntityBeanDeserializer.this.value);
405 }
406 }
407
408 public void setReal(Object target) throws SAXException {
409 try {
410 if (index < 0)
411 pd.getWriteMethod().invoke(target, new Object[] { value });
412 else
413 pd.getWriteMethod().invoke(target, new Object[] { new Integer(index), value });
414 } catch (Exception e) {
415 Class type = pd.getReadMethod().getReturnType();
416 value = JavaUtils.convert(value, type);
417 try {
418 if (index < 0)
419 pd.getWriteMethod().invoke(target, new Object[] { value });
420 else
421 pd.getWriteMethod().invoke(target, new Object[] { new Integer(index), value });
422 } catch (Exception ex) {
423 throw new SAXException(ex);
424 }
425 }
426 }
427 }
428
429 static class BeanPropertyDescriptor {
430 private String name;
431 private Method getter;
432 private Method setter;
433
434 public BeanPropertyDescriptor(String _name, Method _getter, Method _setter) {
435 name = _name;
436 getter = _getter;
437 setter = _setter;
438 }
439
440 public Method getReadMethod() {
441 return getter;
442 }
443 public Method getWriteMethod() {
444 return setter;
445 }
446 public String getName() {
447 return name;
448 }
449 public Class getType() {
450 return getter.getReturnType();
451 }
452
453 /**
454 * This method attempts to sort the property descriptors to match the
455 * order defined in the class. This is necessary to support
456 * xsd:sequence processing, which means that the serialized order of
457 * properties must match the xml element order. (This method assumes that the
458 * order of the set methods matches the xml element order...the emitter
459 * will always order the set methods according to the xml order.)
460 *
461 * This routine also looks for set(i, type) and get(i) methods and adjusts the
462 * property to use these methods instead. These methods are generated by the
463 * emitter for "collection" of properties (i.e. maxOccurs="unbounded" on an element).
464 * JAX-RPC is silent on this issue, but web services depend on this kind of behaviour.
465 * The method signatures were chosen to match bean indexed properties.
466 */
467 static BeanPropertyDescriptor[] processPropertyDescriptors(
468 PropertyDescriptor[] rawPd,
469 Class cls) {
470 BeanPropertyDescriptor[] myPd = new BeanPropertyDescriptor[rawPd.length];
471
472 for (int i = 0; i < rawPd.length; i++) {
473 myPd[i] =
474 new BeanPropertyDescriptor(
475 rawPd[i].getName(),
476 rawPd[i].getReadMethod(),
477 rawPd[i].getWriteMethod());
478 }
479
480 try {
481 // Create a new pd array and index into the array
482 int index = 0;
483
484 // Build a new pd array
485 // defined by the order of the get methods.
486 BeanPropertyDescriptor[] newPd = new BeanPropertyDescriptor[rawPd.length];
487 Method[] methods = cls.getMethods();
488 for (int i = 0; i < methods.length; i++) {
489 Method method = methods[i];
490 if (method.getName().startsWith("set")) {
491 boolean found = false;
492 for (int j = 0; j < myPd.length && !found; j++) {
493 if (myPd[j].getWriteMethod() != null
494 && myPd[j].getWriteMethod().equals(method)) {
495 found = true;
496 newPd[index] = myPd[j];
497 index++;
498 }
499 }
500 }
501 }
502 // Now if there are any additional property descriptors, add them to the end.
503 if (index < myPd.length) {
504 for (int m = 0; m < myPd.length && index < myPd.length; m++) {
505 boolean found = false;
506 for (int n = 0; n < index && !found; n++) {
507 found = (myPd[m] == newPd[n]);
508 }
509 if (!found) {
510 newPd[index] = myPd[m];
511 index++;
512 }
513 }
514 }
515 // If newPd has same number of elements as myPd, use newPd.
516 if (index == myPd.length) {
517 myPd = newPd;
518 }
519
520 // Get the methods of the class and look for the special set and
521 // get methods for property "collections"
522 for (int i = 0; i < methods.length; i++) {
523 if (methods[i].getName().startsWith("set")
524 && methods[i].getParameterTypes().length == 2) {
525 for (int j = 0; j < methods.length; j++) {
526 if ((methods[j].getName().startsWith("get")
527 || methods[j].getName().startsWith("is"))
528 && methods[j].getParameterTypes().length == 1
529 && methods[j].getReturnType() == methods[i].getParameterTypes()[1]
530 && methods[j].getParameterTypes()[0] == int.class
531 && methods[i].getParameterTypes()[0] == int.class) {
532 for (int k = 0; k < myPd.length; k++) {
533 if (myPd[k].getReadMethod() != null
534 && myPd[k].getWriteMethod() != null
535 && myPd[k].getReadMethod().getName().equals(methods[j].getName())
536 && myPd[k].getWriteMethod().getName().equals(methods[i].getName())) {
537 myPd[k] = new BeanPropertyDescriptor(myPd[k].getName(), methods[j], methods[i]);
538 }
539 }
540 }
541 }
542 }
543 }
544 } catch (Exception e) {
545 // Don't process Property Descriptors if problems occur
546 return myPd;
547 }
548 return myPd;
549 }
550 }
551
552 /**
553 * Create a BeanPropertyDescriptor array for the indicated class.
554 */
555 public static BeanPropertyDescriptor[] getPd(Class javaType) {
556 BeanPropertyDescriptor[] pd;
557 try {
558 PropertyDescriptor[] rawPd =
559 Introspector.getBeanInfo(javaType).getPropertyDescriptors();
560 pd = BeanPropertyDescriptor.processPropertyDescriptors(rawPd, javaType);
561 } catch (Exception e) {
562 // this should never happen
563 throw new RuntimeException(e.getMessage());
564 }
565 return pd;
566 }
567
568 }