View Javadoc
1   /*
2    * Copyright 2010 Philipp Erlacher
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5    * in compliance with the License. You may obtain a copy of the License at
6    *
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software distributed under the License
10   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11   * or implied. See the License for the specific language governing permissions and limitations under
12   * the License.
13   */
14  package org.exolab.castor.xml;
15  
16  import java.io.PrintWriter;
17  import java.io.StringWriter;
18  import java.text.MessageFormat;
19  import java.util.ArrayList;
20  import java.util.Enumeration;
21  import java.util.List;
22  import java.util.Locale;
23  import java.util.ResourceBundle;
24  import java.util.StringTokenizer;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.exolab.castor.mapping.FieldHandler;
29  import org.exolab.castor.mapping.MapItem;
30  import org.exolab.castor.xml.UnmarshalHandler.ArrayHandler;
31  import org.exolab.castor.xml.util.XMLFieldDescriptorImpl;
32  import org.xml.sax.ContentHandler;
33  import org.xml.sax.SAXException;
34  
35  /**
36   * A processor that assists {@link UnmarshalHandler} in dealing with the SAX 2
37   * {@link ContentHandler#endElement(String, String, String)} callback method.
38   * 
39   * @author <a href=" mailto:philipp.erlacher AT gmail DOT com">Philipp Erlacher</a>
40   * @since 1.3.2
41   */
42  public class EndElementProcessor {
43  
44    /**
45     * Standard logger to use.
46     */
47    private static final Log LOG = LogFactory.getLog(EndElementProcessor.class);
48  
49    /**
50     * resource bundle
51     */
52    protected static ResourceBundle resourceBundle;
53  
54    /**
55     * Callback {@link UnmarshalHandler} reference to set the actual state on this instance.
56     */
57    private final UnmarshalHandler _unmarshalHandler;
58  
59    static {
60      resourceBundle = ResourceBundle.getBundle("UnmarshalHandlerMessages", Locale.getDefault());
61    }
62  
63    /**
64     * Creates an instance of this class, with a reference to the actual {@link UnmarshalHandler} for
65     * which this processor deals with the SAX 2 endElement() callback method.
66     * 
67     * @param unmarshalHandler The {@link UnmarshalHandler} instance on which the results of
68     *        processing the endElement method will be 'persisted'/set.
69     */
70    public EndElementProcessor(final UnmarshalHandler unmarshalHandler) {
71      _unmarshalHandler = unmarshalHandler;
72    }
73  
74    public void compute(String name) throws org.xml.sax.SAXException {
75      if (LOG.isTraceEnabled()) {
76        String trace = MessageFormat.format(
77            resourceBundle.getString("unmarshalHandler.log.trace.endElement"), new Object[] {name});
78        LOG.trace(trace);
79      }
80  
81      // -- If we are skipping elements that have appeared in the XML but for
82      // -- which we have no mapping, decrease the ignore depth counter and
83      // return
84      if (_unmarshalHandler.getStrictElementHandler().skipEndElement()) {
85        return;
86      }
87  
88      // -- Do delagation if necessary
89      if (_unmarshalHandler.getAnyNodeHandler().hasAnyUnmarshaller()) {
90        _unmarshalHandler.getAnyNodeHandler().endElement(name);
91        // we are back to the starting node
92        if (_unmarshalHandler.getAnyNodeHandler().isStartingNode()) {
93          _unmarshalHandler.setAnyNode(_unmarshalHandler.getAnyNodeHandler().getStartingNode());
94        } else
95          return;
96      }
97  
98      if (_unmarshalHandler.getStateStack().isEmpty()) {
99        String err = MessageFormat.format(
100           resourceBundle.getString("unmarshalHandler.error.missing.startElement"),
101           new Object[] {name});
102       throw new SAXException(err);
103     }
104 
105     // -- * Begin Namespace Handling
106     // -- XXX Note: This code will change when we update the XML event API
107 
108     int idx = name.indexOf(':');
109     if (idx >= 0) {
110       name = name.substring(idx + 1);
111     }
112     // -- * End Namespace Handling
113 
114     UnmarshalState state = _unmarshalHandler.getStateStack().removeLastState();
115 
116     // -- make sure we have the correct closing tag
117     XMLFieldDescriptor descriptor = state.getFieldDescriptor();
118 
119     if (!state.getElementName().equals(name)) {
120 
121       // maybe there is still a container to end
122       if (descriptor.isContainer()) {
123         _unmarshalHandler.getStateStack().pushState(state);
124         // -- check for possible characters added to
125         // -- the container's state that should
126         // -- really belong to the parent state
127         StringBuffer tmpBuffer = null;
128         if (state.getBuffer() != null) {
129           if (!UnmarshalHandler.isWhitespace(state.getBuffer())) {
130             if (state.getClassDescriptor().getContentDescriptor() == null) {
131               tmpBuffer = state.getBuffer();
132               state.setBuffer(null);
133             }
134           }
135         }
136         // -- end container
137         _unmarshalHandler.endElement(state.getElementName());
138 
139         if (tmpBuffer != null) {
140           state = _unmarshalHandler.getStateStack().getLastState();
141           if (state.getBuffer() == null)
142             state.setBuffer(tmpBuffer);
143           else
144             state.getBuffer().append(tmpBuffer.toString());
145         }
146         _unmarshalHandler.endElement(name);
147         return;
148       }
149       String err = MessageFormat.format(
150           resourceBundle.getString("unmarshalHandler.error.different.endElement.expected"),
151           new Object[] {state.getElementName(), name});
152       throw new SAXException(err);
153     }
154 
155     // -- clean up current Object
156     Class<?> type = state.getType();
157 
158     if (type == null) {
159       if (!state.isWrapper()) {
160         // -- this message will only show up if debug
161         // -- is turned on...how should we handle this case?
162         // -- should it be a fatal error?
163         String info = MessageFormat.format(
164             resourceBundle.getString("unmarshalHandler.log.info.no.Descriptor.found"),
165             new Object[] {state.getElementName()});
166         LOG.info(info);
167       }
168 
169       // -- handle possible location text content
170       // -- TODO: cleanup location path support.
171       // -- the following code needs to be improved as
172       // -- for searching descriptors in this manner can
173       // -- be slow
174       StringBuffer tmpBuffer = null;
175       if (state.getBuffer() != null) {
176         if (!UnmarshalHandler.isWhitespace(state.getBuffer())) {
177           tmpBuffer = state.getBuffer();
178           state.setBuffer(null);
179         }
180       }
181       if (tmpBuffer != null) {
182         UnmarshalState targetState = state;
183         String locPath = targetState.getElementName();
184         while ((targetState = targetState.getParent()) != null) {
185           if ((targetState.isWrapper()) || (targetState.getClassDescriptor() == null)) {
186             locPath = targetState.getElementName() + "/" + locPath;
187             continue;
188           }
189 
190           XMLFieldDescriptor tmpDesc = targetState.getClassDescriptor().getContentDescriptor();
191           if (tmpDesc != null && locPath.equals(tmpDesc.getLocationPath())) {
192             if (targetState.getBuffer() == null)
193               targetState.setBuffer(tmpBuffer);
194             else
195               targetState.getBuffer().append(tmpBuffer.toString());
196           }
197         }
198       }
199 
200       // -- remove current namespace scoping
201       _unmarshalHandler.getNamespaceHandling().removeCurrentNamespaceInstance();
202       return;
203     }
204 
205     // -- check for special cases
206     boolean byteArray = false;
207     if (type.isArray()) {
208       byteArray = (type.getComponentType() == Byte.TYPE);
209     }
210 
211     // -- If we don't have an instance object and the Class type
212     // -- is not a primitive or a byte[] we must simply return
213     if ((state.getObject() == null) && (!state.isPrimitiveOrImmutable())) {
214       // -- remove current namespace scoping
215       _unmarshalHandler.getNamespaceHandling().removeCurrentNamespaceInstance();
216       return;
217     }
218 
219     // / DEBUG System.out.println("end: " + name);
220 
221     if (state.isPrimitiveOrImmutable()) {
222 
223       String str = null;
224 
225       if (state.getBuffer() != null) {
226         str = state.getBuffer().toString();
227         state.getBuffer().setLength(0);
228       }
229 
230       if (type == String.class && !((XMLFieldDescriptorImpl) descriptor).isDerivedFromXSList()) {
231         if (str != null)
232           state.setObject(str);
233         else if (state.isNil()) {
234           state.setObject(null);
235         } else {
236           state.setObject("");
237         }
238       }
239       // -- special handling for byte[]
240       else if (byteArray && !descriptor.isDerivedFromXSList()) {
241         if (str == null)
242           state.setObject(new byte[0]);
243         else {
244           state.setObject(_unmarshalHandler.decodeBinaryData(descriptor, str));
245         }
246       } else if (state.getConstructorArguments() != null) {
247         state.setObject(
248             _unmarshalHandler.createInstance(state.getType(), state.getConstructorArguments()));
249       } else if (descriptor.isMultivalued() && descriptor.getSchemaType() != null
250           && descriptor.getSchemaType().equals("list")
251           && ((XMLFieldDescriptorImpl) descriptor).isDerivedFromXSList()) {
252         StringTokenizer attrValueTokenizer = new StringTokenizer(str);
253         List<Object> primitives = new ArrayList<Object>();
254         while (attrValueTokenizer.hasMoreTokens()) {
255           String tokenValue = attrValueTokenizer.nextToken();
256           if (MarshalFramework.isPrimitive(descriptor.getFieldType())) {
257             primitives.add(
258                 _unmarshalHandler.toPrimitiveObject(type, tokenValue, state.getFieldDescriptor()));
259           } else {
260             Class<?> valueType = descriptor.getFieldType();
261             // -- handle base64/hexBinary
262             if (valueType.isArray() && (valueType.getComponentType() == Byte.TYPE)) {
263               primitives.add(_unmarshalHandler.decodeBinaryData(descriptor, tokenValue));
264             }
265           }
266 
267         }
268         state.setObject(primitives);
269       } else {
270         if (state.isNil()) {
271           state.setObject(null);
272         } else {
273           state.setObject(
274               _unmarshalHandler.toPrimitiveObject(type, str, state.getFieldDescriptor()));
275         }
276       }
277     } else if (ArrayHandler.class.isAssignableFrom(state.getType())) {
278       state.setObject(((ArrayHandler) state.getObject()).getObject());
279       state.setType(state.getObject().getClass());
280 
281     }
282 
283     // -- check for character content
284     if ((state.getBuffer() != null) && (state.getBuffer().length() > 0)
285         && (state.getClassDescriptor() != null)) {
286       XMLFieldDescriptor cdesc = state.getClassDescriptor().getContentDescriptor();
287       if (cdesc != null) {
288         Object value = state.getBuffer().toString();
289         if (MarshalFramework.isPrimitive(cdesc.getFieldType()))
290           value = _unmarshalHandler.toPrimitiveObject(cdesc.getFieldType(), (String) value,
291               state.getFieldDescriptor());
292         else {
293           Class<?> valueType = cdesc.getFieldType();
294           // -- handle base64/hexBinary
295           if (valueType.isArray() && (valueType.getComponentType() == Byte.TYPE)) {
296             value = _unmarshalHandler.decodeBinaryData(descriptor, (String) value);
297           }
298         }
299 
300         try {
301           FieldHandler handler = cdesc.getHandler();
302           boolean addObject = true;
303           if (_unmarshalHandler.isReuseObjects()) {
304             // -- check to see if we need to
305             // -- add the object or not
306             Object tmp = handler.getValue(state.getObject());
307             if (tmp != null) {
308               // -- Do not add object if values
309               // -- are equal
310               addObject = (!tmp.equals(value));
311             }
312           }
313           if (addObject)
314             handler.setValue(state.getObject(), value);
315         } catch (java.lang.IllegalStateException ise) {
316           String err = MessageFormat.format(
317               resourceBundle.getString("unmarshalHandler.error.unable.add.text"),
318               new Object[] {descriptor.getXMLName(), ise.toString()});
319           throw new SAXException(err, ise);
320         }
321       }
322       // -- Handle references
323       else if (descriptor.isReference()) {
324         UnmarshalState pState = _unmarshalHandler.getStateStack().getLastState();
325         _unmarshalHandler.processIDREF(state.getBuffer().toString(), descriptor,
326             pState.getObject());
327         _unmarshalHandler.getNamespaceHandling().removeCurrentNamespaceInstance();
328         return;
329       } else {
330         // -- check for non-whitespace...and report error
331         if (!UnmarshalHandler.isWhitespace(state.getBuffer())) {
332           String err =
333               MessageFormat.format(resourceBundle.getString("unmarshalHandler.error.illegal.text"),
334                   new Object[] {name, state.getBuffer()});
335           throw new SAXException(err);
336         }
337       }
338     }
339 
340     // -- We're finished processing the object, so notify the
341     // -- Listener (if any).
342     Object stateObject = state.getObject();
343     Object parentObject = (state.getParent() == null) ? null : state.getParent().getObject();
344     _unmarshalHandler.getDelegateUnmarshalListener().unmarshalled(stateObject, parentObject);
345 
346     // -- if we are at root....just validate and we are done
347     if (_unmarshalHandler.getStateStack().isEmpty()) {
348       if (_unmarshalHandler.isValidating()) {
349         ValidationException first = null;
350         ValidationException last = null;
351 
352         // -- check unresolved references
353         if (_unmarshalHandler.getResolveTable() != null
354             && !_unmarshalHandler.getInternalContext().getLenientIdValidation()) {
355           Enumeration enumeration = _unmarshalHandler.getResolveTable().keys();
356           while (enumeration.hasMoreElements()) {
357             Object ref = enumeration.nextElement();
358             // if
359             // (ref.toString().startsWith(MapItem.class.getName()))
360             // continue;
361             String msg = "unable to resolve reference: " + ref;
362             if (first == null) {
363               first = new ValidationException(msg);
364               last = first;
365             } else {
366               last.setNext(new ValidationException(msg));
367               last = last.getNext();
368             }
369           }
370         }
371         try {
372           Validator validator = new Validator();
373           ValidationContext context = new ValidationContext();
374           context.setInternalContext(_unmarshalHandler.getInternalContext());
375           validator.validate(state.getObject(), context);
376           if (!_unmarshalHandler.getInternalContext().getLenientIdValidation()) {
377             validator.checkUnresolvedIdrefs(context);
378           }
379           context.cleanup();
380         } catch (ValidationException vEx) {
381           if (first == null)
382             first = vEx;
383           else
384             last.setNext(vEx);
385         }
386         if (first != null) {
387           throw new SAXException(first);
388         }
389       }
390       return;
391     }
392 
393     // -- Add object to parent if necessary
394 
395     if (descriptor.isIncremental()) {
396       // -- remove current namespace scoping
397       _unmarshalHandler.getNamespaceHandling().removeCurrentNamespaceInstance();
398       return; // -- already added
399     }
400 
401     Object val = state.getObject();
402 
403     // --special code for AnyNode handling
404     if (_unmarshalHandler.getAnyNode() != null) {
405       val = _unmarshalHandler.getAnyNode();
406       _unmarshalHandler.setAnyNode(null);
407     }
408 
409     // -- save fieldState
410     UnmarshalState fieldState = state;
411 
412     // -- have we seen this object before?
413     boolean firstOccurance = false;
414 
415     // -- get target object
416     state = _unmarshalHandler.getStateStack().getLastState();
417     if (state.isWrapper()) {
418       state = fieldState.getTargetState();
419     }
420 
421     // -- check to see if we have already read in
422     // -- an element of this type.
423     // -- (Q: if we have a container, do we possibly need to
424     // -- also check the container's multivalued status?)
425     if (!descriptor.isMultivalued()) {
426 
427       if (state.isUsed(descriptor)) {
428 
429         String location = name;
430         while (!_unmarshalHandler.getStateStack().isEmpty()) {
431           UnmarshalState tmpState = _unmarshalHandler.getStateStack().removeLastState();
432           if (!tmpState.isWrapper()) {
433             if (tmpState.getFieldDescriptor().isContainer())
434               continue;
435           }
436           location = state.getElementName() + "/" + location;
437         }
438 
439         String err = MessageFormat.format(
440             resourceBundle.getString("unmarshalHandler.error.element.occurs.more.than.once"),
441             new Object[] {name, state.getType().getName(), location});
442 
443         ValidationException vx = new ValidationException(err);
444 
445         throw new SAXException(vx);
446       }
447       state.markAsUsed(descriptor);
448       // -- if this is the identity then save id
449       if (state.getClassDescriptor().getIdentity() == descriptor) {
450         state.setKey(val);
451       }
452     } else {
453       // -- check occurance of descriptor
454       if (!state.isUsed(descriptor)) {
455         firstOccurance = true;
456       }
457 
458       // -- record usage of descriptor
459       state.markAsUsed(descriptor);
460     }
461 
462     try {
463       FieldHandler handler = descriptor.getHandler();
464       // check if the value is a QName that needs to
465       // be resolved (ns:value -> {URI}value)
466       String valueType = descriptor.getSchemaType();
467       if ((valueType != null) && (valueType.equals(MarshalFramework.QNAME_NAME))) {
468         val = _unmarshalHandler.getNamespaceHandling().resolveNamespace(val);
469       }
470 
471       boolean addObject = true;
472       if (_unmarshalHandler.isReuseObjects() && fieldState.isPrimitiveOrImmutable()) {
473         // -- check to see if we need to
474         // -- add the object or not
475         Object tmp = handler.getValue(state.getObject());
476         if (tmp != null) {
477           // -- Do not add object if values
478           // -- are equal
479           addObject = (!tmp.equals(val));
480         }
481       }
482 
483       // -- special handling for mapped objects
484       if (descriptor.isMapped()) {
485         if (!(val instanceof MapItem)) {
486           MapItem mapItem = new MapItem(fieldState.getKey(), val);
487           val = mapItem;
488         } else {
489           // -- make sure value exists (could be a reference)
490           MapItem mapItem = (MapItem) val;
491           if (mapItem.getValue() == null) {
492             // -- save for later...
493             addObject = false;
494             _unmarshalHandler.addReference(mapItem.toString(), state.getObject(), descriptor);
495           }
496         }
497       }
498 
499       if (addObject) {
500         // -- clear any collections if necessary
501         if (firstOccurance && _unmarshalHandler.isClearCollections()) {
502           handler.resetValue(state.getObject());
503         }
504 
505         if (descriptor.isMultivalued() && descriptor.getSchemaType() != null
506             && descriptor.getSchemaType().equals("list")
507             && ((XMLFieldDescriptorImpl) descriptor).isDerivedFromXSList()) {
508           List<Object> values = (List<Object>) val;
509           for (Object value : values) {
510             // -- finally set the value!!
511             handler.setValue(state.getObject(), value);
512 
513             // If there is a parent for this object, pass along
514             // a notification that we've finished adding a child
515             _unmarshalHandler.getDelegateUnmarshalListener().fieldAdded(descriptor.getFieldName(),
516                 state.getObject(), fieldState.getObject());
517           }
518         } else {
519 
520           // -- finally set the value!!
521           handler.setValue(state.getObject(), val);
522 
523           // If there is a parent for this object, pass along
524           // a notification that we've finished adding a child
525           _unmarshalHandler.getDelegateUnmarshalListener().fieldAdded(descriptor.getFieldName(),
526               state.getObject(), fieldState.getObject());
527         }
528       }
529 
530     }
531     /*
532      * catch(java.lang.reflect.InvocationTargetException itx) {
533      * 
534      * Throwable toss = itx.getTargetException(); if (toss == null) toss = itx;
535      * 
536      * String err = "unable to add '" + name + "' to <"; err += state.descriptor.getXMLName(); err
537      * += "> due to the following exception: " + toss; throw new SAXException(err); }
538      */
539     catch (Exception ex) {
540       StringWriter sw = new StringWriter();
541       PrintWriter pw = new PrintWriter(sw);
542       ex.printStackTrace(pw);
543       pw.flush();
544       String err = MessageFormat.format(
545           resourceBundle.getString("unmarshalHandler.error.unable.add.element"),
546           new Object[] {name, state.getFieldDescriptor().getXMLName(), sw.toString()});
547       throw new SAXException(err, ex);
548     }
549 
550     // -- remove current namespace scoping
551     _unmarshalHandler.getNamespaceHandling().removeCurrentNamespaceInstance();
552 
553     // remove additional (artifical aka container) state introduced for
554     // single-valued (iow maxOccurs="1") choices.
555     if (state.getFieldDescriptor().isContainer() && state.getClassDescriptor().isChoice()
556         && !state.getFieldDescriptor().isMultivalued()) {
557       _unmarshalHandler.endElement(state.getElementName());
558     }
559 
560   }
561 }