package ecologylab.serialization.deserializers.pullhandlers.stringformats; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.util.Collection; import java.util.Map; import javax.xml.stream.XMLStreamException; import org.codehaus.jackson.JsonParseException; import ecologylab.platformspecifics.FundamentalPlatformSpecifics; import ecologylab.serialization.ClassDescriptor; import ecologylab.serialization.DeserializationHookStrategy; import ecologylab.serialization.ElementState; import ecologylab.serialization.FieldDescriptor; import ecologylab.serialization.FieldType; import ecologylab.serialization.SIMPLTranslationException; import ecologylab.serialization.SimplTypesScope; import ecologylab.serialization.TranslationContext; import ecologylab.serialization.types.element.IMappable; /** * Pull API implementation to transform XML documents to corresponding object models. Utilizes * XMLStreamReader to get sequential access to tags in XML. * * @author nabeel */ public class XMLPullDeserializer extends StringPullDeserializer { // private CharSequence test; XMLParser xmlParser; /** * * @param translationScope * @param translationContext * @param deserializationHookStrategy */ public XMLPullDeserializer( SimplTypesScope translationScope, TranslationContext translationContext, DeserializationHookStrategy<? extends Object, ? extends FieldDescriptor> deserializationHookStrategy) { super(translationScope, translationContext, deserializationHookStrategy); } /** * * @param translationScope * @param translationContext */ public XMLPullDeserializer(SimplTypesScope translationScope, TranslationContext translationContext) { super(translationScope, translationContext); } @Override public Object parse(InputStream inputStream, Charset charSet) throws SIMPLTranslationException { Object result = null; try { configure(inputStream, charSet); result = parse(); return result; } catch(SIMPLTranslationException ex) { throw ex; } catch (Exception ex) { SIMPLTranslationException ste = new SIMPLTranslationException("exception occurred in deserialzation ", ex); ste.setRemnantObject(result); throw ste; } } @Override public Object parse(InputStream inputStream) throws SIMPLTranslationException { // hold onto a black result to return. Object result = null; try { configure(inputStream); result = parse(); return result; } catch(SIMPLTranslationException ex) { throw ex; } catch (Exception ex) { SIMPLTranslationException toThrow = new SIMPLTranslationException("exception occurred in deserialzation ", ex); toThrow.setRemnantObject(result); throw toThrow; } } /** * Parses a charsequence of the XML document and returns the corresponding object model. * * @param charSequence * @return * @throws SIMPLTranslationException * @throws XMLStreamException * @throws FactoryConfigurationError * @throws SIMPLTranslationException * @throws IOException */ @Override public Object parse(CharSequence charSequence) throws SIMPLTranslationException { // hold onto a black result to return. Object result = null; try { configure(charSequence); result = parse(); return result; } catch(SIMPLTranslationException ex) { throw ex; } catch (Exception ex) { SIMPLTranslationException ste = new SIMPLTranslationException("exception occurred in deserialzation ", ex); ste.setRemnantObject(result); throw ste; } } /** * Configures the input stream. Creates an instance of XMLStreamReader on the input stream. * * @param inputStream * @param charSet * @throws XMLStreamException * @throws FactoryConfigurationError */ private void configure(InputStream inputStream, Charset charSet) throws SIMPLTranslationException { xmlParser = FundamentalPlatformSpecifics.get().getXMLParser(inputStream, charSet); } /** * * @param inputStream * @throws XMLStreamException * @throws FactoryConfigurationError */ private void configure(InputStream inputStream) throws SIMPLTranslationException { xmlParser = FundamentalPlatformSpecifics.get().getXMLParser(inputStream); } /** * * @param charSequence * @throws XMLStreamException * @throws FactoryConfigurationError */ private void configure(CharSequence charSequence) throws SIMPLTranslationException { xmlParser = FundamentalPlatformSpecifics.get().getXMLParser(charSequence); } /** * * @return * @throws XMLStreamException * @throws SIMPLTranslationException * @throws IOException */ private Object parse() throws SIMPLTranslationException, IOException { Object root = null; nextEvent(); // We should expect the first element to be the START if (xmlParser.getEventType() != XMLParser.START_ELEMENT) { throw new SIMPLTranslationException("start of an element expected"); } String rootTag = getTagName(); ClassDescriptor<? extends FieldDescriptor> rootClassDescriptor = translationScope .getClassDescriptorByTag(rootTag); if (rootClassDescriptor == null) { throw new SIMPLTranslationException("cannot find the class descriptor for root element <" + rootTag + ">; make sure if translation scope is correct."); } root = rootClassDescriptor.getInstance(); // Logic to set all field descritpro scalars to defaults. for(FieldDescriptor fd : rootClassDescriptor.allFieldDescriptors()) { if(fd.isScalar() && (fd.isEnum() == false)) { fd.setFieldToScalarDefault(root, translationContext); } } deserializationPreHook(root, translationContext); if (deserializationHookStrategy != null) deserializationHookStrategy.deserializationPreHook(root, null); deserializeAttributes(root, rootClassDescriptor); deserializationInHook(root, translationContext); if (deserializationHookStrategy != null) deserializationHookStrategy.deserializationInHook(root, null); createObjectModel(root, rootClassDescriptor, rootTag); // Post hook is called at the end of createObjectModel. // That should be pulled here at some point. return root; } /** * Recursive method that moves forward in the CharSequence through XMLStreamReader to create a * corresponding object model * * @param root * instance of the root element created by the calling method * @param rootClassDescriptor * instance of the classdescriptor of the root element created by the calling method * @param tagName * TODO * @throws JsonParseException * @throws IOException * @throws SIMPLTranslationException * @throws XMLStreamException */ private void createObjectModel(Object root, ClassDescriptor<? extends FieldDescriptor> rootClassDescriptor, String rootTag) throws IOException, SIMPLTranslationException { int event = 0; event = nextEvent(); FieldDescriptor currentFieldDescriptor = null; // new FieldDescriptor(); String xmlText = ""; while ( event!= XMLParser.END_DOCUMENT && (event != XMLParser.END_ELEMENT || !rootTag.equals(getTagName()))) { if (event != XMLParser.START_ELEMENT) { if (event == XMLParser.CHARACTERS) { xmlText += xmlParser.getText(); } else if (event == XMLParser.END_ELEMENT && currentFieldDescriptor != null && currentFieldDescriptor.getType() == FieldType.WRAPPER) { currentFieldDescriptor = currentFieldDescriptor.getWrappedFD(); } event = nextEvent(); continue; } String tag = getTagName(); currentFieldDescriptor = currentFieldDescriptor != null &¤tFieldDescriptor.getType() == FieldType.WRAPPER ? currentFieldDescriptor.getWrappedFD() : rootClassDescriptor.getFieldDescriptorByTag(tag, translationScope, null); if (currentFieldDescriptor == null) { currentFieldDescriptor = FieldDescriptor.makeIgnoredFieldDescriptor(tag); } FieldType fieldType = currentFieldDescriptor.getType(); switch (fieldType) { case SCALAR: // If we don't find a field declaration in the serialized representation for a scalar field... // We'll never end up changing its value. So instead, let's automatically set every scalar value to its corresponding default value first. event = deserializeScalar(root, currentFieldDescriptor); break; case COLLECTION_SCALAR: event = deserializeScalarCollection(root, currentFieldDescriptor); break; case COMPOSITE_ELEMENT: event = deserializeComposite(root, currentFieldDescriptor); break; case COLLECTION_ELEMENT: event = deserializeCompositeCollection(root, currentFieldDescriptor); break; case MAP_ELEMENT: event = deserializeCompositeMap(root, currentFieldDescriptor); break; case WRAPPER: event = nextEvent(); break; case IGNORED_ELEMENT: event = ignoreTag(tag); break; default: event = nextEvent(); } if (event == XMLParser.END_DOCUMENT) { // no more data? but we are expecting so its not correct throw new SIMPLTranslationException( "premature end of file: check XML file for consistency"); } } if (rootClassDescriptor.hasScalarFD()) { rootClassDescriptor.getScalarTextFD().setFieldToScalar(root, xmlText, translationContext); } deserializationPostHook(root, translationContext); if (deserializationHookStrategy != null) deserializationHookStrategy.deserializationPostHook(root, currentFieldDescriptor == null || currentFieldDescriptor.getType() == FieldType.IGNORED_ELEMENT ? null : currentFieldDescriptor); // deserializationHookStrategy.deserializationPostHook(root, null); } /** * * @param root * @param fd * @return * @throws SIMPLTranslationException * @throws XMLStreamException */ private int deserializeScalarCollection(Object root, FieldDescriptor fd) throws SIMPLTranslationException { int event = xmlParser.getEventType(); String tagName = getTagName(); if (!fd.isCollectionTag(tagName)) { event = ignoreTag(tagName); } else { while (fd.isCollectionTag(tagName)) { if (event != XMLParser.START_ELEMENT) { // end of collection break; } event = xmlParser.next(); if (event == XMLParser.CHARACTERS && event != XMLParser.END_ELEMENT) { StringBuilder text = new StringBuilder(); text.append(xmlParser.getText()); while (xmlParser.next() != XMLParser.END_ELEMENT) { if (xmlParser.getEventType() == XMLParser.CHARACTERS) text.append(xmlParser.getText()); } String value = text.toString(); fd.addLeafNodeToCollection(root, value, translationContext); } event = xmlParser.nextTag(); tagName = getTagName(); } } return event; } /** * * @param root * @param currentFieldDescriptor * @return * @throws SIMPLTranslationException * @throws IOException * @throws XMLStreamException */ private int deserializeComposite(Object root, FieldDescriptor currentFieldDescriptor) throws SIMPLTranslationException, IOException { String tagName = getTagName(); Object subRoot = getSubRoot(currentFieldDescriptor, tagName, root); currentFieldDescriptor.setFieldToComposite(root, subRoot); return nextEvent(); } /** * * @param root * @param fd * @return * @throws SIMPLTranslationException * @throws IOException * @throws XMLStreamException */ private int deserializeCompositeMap(Object root, FieldDescriptor fd) throws SIMPLTranslationException, IOException { Object subRoot; int event = xmlParser.getEventType(); while (fd.isCollectionTag(getTagName())) { if (event != XMLParser.START_ELEMENT) { // end of collection break; } String compositeTagName = getTagName(); subRoot = getSubRoot(fd, compositeTagName, root); final Object key = (subRoot instanceof IMappable<?>) ? ((IMappable<?>) subRoot).key() : fd.getMapKeyFieldValue(subRoot); if (key != null) { Map map = (Map) fd.automaticLazyGetCollectionOrMap(root); map.put(key, subRoot); } event = xmlParser.nextTag(); } return event; } /** * * @param root * @param fd * @return * @throws SIMPLTranslationException * @throws IOException * @throws XMLStreamException */ private int deserializeCompositeCollection(Object root, FieldDescriptor fd) throws SIMPLTranslationException, IOException { Object subRoot; int event = xmlParser.getEventType(); String tagName = getTagName(); if (!fd.isCollectionTag(tagName)) { event = ignoreTag(tagName); } else { while (fd.isCollectionTag(tagName)) { if (event != XMLParser.START_ELEMENT) { // end of collection break; } subRoot = getSubRoot(fd, tagName, root); Collection collection = (Collection) fd.automaticLazyGetCollectionOrMap(root); collection.add(subRoot); event = xmlParser.nextTag(); tagName = getTagName(); } } return event; } /** * * @param root * @param currentFieldDescriptor * @return * @throws XMLStreamException */ private int deserializeScalar(Object root, FieldDescriptor currentFieldDescriptor) throws SIMPLTranslationException { StringBuilder text = new StringBuilder(); do { if (xmlParser.getEventType() == XMLParser.CHARACTERS) text.append(xmlParser.getText()); } while (nextEvent() != XMLParser.END_ELEMENT); String value = text.toString(); currentFieldDescriptor.setFieldToScalar(root, value, translationContext); return nextEvent(); } /** * * @param tag * @return * @throws XMLStreamException */ private int ignoreTag(String tag) throws SIMPLTranslationException { int event = -1; println("ignoring tag: " + tag); while (event != XMLParser.END_ELEMENT || !getTagName().equals(tag)) event = nextEvent(); return nextEvent(); } /** * Gets the sub root of the object model if its a composite object. Does graph handling/ Handles * simpl:ref tag to assign an already created instance of the composite object instead of creating * a new one * * @param currentFieldDescriptor * @param root * TODO * @return * @throws SIMPLTranslationException * @throws JsonParseException * @throws IOException * @throws XMLStreamException */ private Object getSubRoot(FieldDescriptor currentFieldDescriptor, String tagName, Object root) throws SIMPLTranslationException, IOException { Object subRoot = null; ClassDescriptor<? extends FieldDescriptor> subRootClassDescriptor = currentFieldDescriptor .getChildClassDescriptor(tagName); String simplReference = null; if ((simplReference = getSimpleReference()) != null) { subRoot = translationContext.getFromMap(simplReference); xmlParser.next(); } else { subRoot = subRootClassDescriptor.getInstance(); // Logic to set all field descritpro scalars to defaults. for(FieldDescriptor fd : subRootClassDescriptor.allFieldDescriptors()) { if(fd.isScalar() && (fd.isEnum() == false)) { fd.setFieldToScalarDefault(subRoot, translationContext); } } deserializationPreHook(subRoot, translationContext); if (deserializationHookStrategy != null) deserializationHookStrategy.deserializationPreHook(subRoot, currentFieldDescriptor); if (subRoot != null) { if (subRoot instanceof ElementState && root instanceof ElementState) { ((ElementState) subRoot).setupInParent((ElementState) root); } } deserializeAttributes(subRoot, subRootClassDescriptor); deserializationInHook(subRoot, translationContext); if (deserializationHookStrategy != null) deserializationHookStrategy.deserializationInHook(subRoot, currentFieldDescriptor); createObjectModel(subRoot, subRootClassDescriptor, tagName); } if (deserializationHookStrategy != null && subRoot != null) { Object newSubRoot= deserializationHookStrategy.changeObjectIfNecessary(subRoot, currentFieldDescriptor); if (newSubRoot != null) subRoot = newSubRoot; } return subRoot; } /** * * @return */ private String getSimpleReference() { String simplReference = null; for (int i = 0; i < xmlParser.getAttributeCount(); i++) { String attributePrefix = xmlParser.getAttributePrefix(i); String tag = xmlParser.getAttributeLocalName(i); String value = xmlParser.getAttributeValue(i); if (TranslationContext.SIMPL.equals(attributePrefix)) { if (tag.equals(TranslationContext.REF)) { simplReference = value; } } } return simplReference; } /** * * @param root * @param rootClassDescriptor * @return */ private boolean deserializeAttributes(Object root, ClassDescriptor<? extends FieldDescriptor> rootClassDescriptor) { for (int i = 0; i < xmlParser.getAttributeCount(); i++) { String attributePrefix = xmlParser.getAttributePrefix(i); String tag = xmlParser.getAttributeLocalName(i); String value = xmlParser.getAttributeValue(i); // If a tag is simpl:... if (TranslationContext.SIMPL.equals(attributePrefix)) { // Handle simpl:id's if (tag.equals(TranslationContext.ID)) { translationContext.markAsUnmarshalled(value, root); } } else { FieldDescriptor attributeFieldDescriptor = rootClassDescriptor.getFieldDescriptorByTag(tag, translationScope); if (attributeFieldDescriptor != null) { attributeFieldDescriptor.setFieldToScalar(root, value, translationContext); } else { debug("ignoring attribute: " + tag); } } } return true; } /** * * @return * @throws XMLStreamException */ private int nextEvent() throws SIMPLTranslationException { int eventType = XMLParser.END_DOCUMENT; // skip events that we don't handle. while ((eventType = xmlParser.next()) != XMLParser.END_DOCUMENT) { if (xmlParser.getEventType() == XMLParser.START_DOCUMENT || xmlParser.getEventType() == XMLParser.START_ELEMENT || xmlParser.getEventType() == XMLParser.END_ELEMENT || xmlParser.getEventType() == XMLParser.END_DOCUMENT || xmlParser.getEventType() == XMLParser.CHARACTERS) { break; } } return eventType; } /** * @throws SIMPLTranslationException * */ protected void debug() { try { int event = xmlParser.getEventType(); switch (event) { case XMLParser.START_ELEMENT: System.out.println(getTagName()); break; case XMLParser.END_ELEMENT: System.out.println(getTagName()); break; case XMLParser.CHARACTERS: System.out.println(xmlParser.getText()); break; case XMLParser.CDATA: System.out.println("cdata " + xmlParser.getText()); break; } // end switch } catch (SIMPLTranslationException e) { e.printStackTrace(); } } /** * * @return */ private String getTagName() { if (xmlParser.getPrefix() != null && xmlParser.getPrefix().length() != 0) return xmlParser.getPrefix() + ":" + xmlParser.getLocalName(); else return xmlParser.getLocalName(); } /** * * @throws XMLStreamException */ protected void printParse() throws SIMPLTranslationException { int event; do { event = xmlParser.getEventType(); switch (event) { case XMLParser.START_ELEMENT: System.out.print("start element: "); System.out.print(xmlParser.getEventType()); System.out.print(" : "); System.out.print(xmlParser.getName()); System.out.println(); break; case XMLParser.END_ELEMENT: System.out.print("end element: "); System.out.print(xmlParser.getEventType()); System.out.print(" : "); System.out.print(xmlParser.getName()); System.out.println(); break; case XMLParser.CHARACTERS: System.out.print("characters: "); System.out.print(xmlParser.getEventType()); System.out.print(" : "); System.out.print(xmlParser.getText()); System.out.println(); break; case XMLParser.CDATA: System.out.println("cdata " + xmlParser.getText()); break; default: System.out.println(xmlParser.getEventType()); } // end switch } while (xmlParser.next() != XMLParser.END_DOCUMENT); } }