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