package protobuf.codec.xml; import java.io.IOException; import java.io.Reader; import java.io.Writer; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; import protobuf.codec.AbstractCodec; import protobuf.codec.ParseException; import protobuf.codec.Codec.Feature; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; import com.google.protobuf.UnknownFieldSet; import com.google.protobuf.Message.Builder; /** * Xml-protobuf serializer/deserializer.Relies on stax(woodstox) as the underlying parser. * Supports {@link UnknownFieldSet}, extension field names are written as xml elements with name being * "extension" an attribute "type" containting the field name and the value as the element value. * In case the {@link Feature#UNKNOWN_FIELDS} is enabled ( this feature is enabled by default) each xml element in * could contain a field by name "unknownfields" or whatever value the {@value Feature#UNKNOWN_FIELD_ELEM_NAME} is * set to. The value of this field is hex encoded byte string. * @author sijuv * */ //TODO Need to make namespace aware public class XmlCodec extends AbstractCodec { private static XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance(); private static XMLInputFactory XML_INPUT_FACTORY = XMLInputFactory.newInstance(); static { XML_INPUT_FACTORY.setProperty("javax.xml.stream.isNamespaceAware", Boolean.FALSE); } /** * Reads the xml stream into a {@link Message} object. The root element of the xml should as per {@link #getRootElementName(Message)}. * Extension fields are identified by element name {@link #EXTENSION_ELEM_NAME} with the attribute {@link #EXTENTION_TYPE_ATTRIB_NAME} identifying * the type of extension. The value of this element is the field value. * @param builder * @param reader * @param extnRegistry * @return * @throws IOException */ @Override protected Message readFromStream(Builder builder, Reader reader, ExtensionRegistry extnRegistry) throws IOException { try { XMLStreamReader xmlreader = XML_INPUT_FACTORY.createXMLStreamReader(reader); return XmlReader.parse(builder, xmlreader, extnRegistry, getAllFeaturesSet()); } catch (XMLStreamException e) { throw new ParseException(e); } } @Override protected void writeToStream(Message message, Writer writer) throws IOException { try { XMLStreamWriter xmlwriter = XML_OUTPUT_FACTORY.createXMLStreamWriter(writer); xmlwriter.writeStartDocument(); xmlwriter.writeStartElement(getRootElementName(message)); XmlWriter.writeXml(message, xmlwriter, getAllFeaturesSet()); xmlwriter.writeEndElement(); xmlwriter.writeEndDocument(); } catch (XMLStreamException e) { throw new ParseException(e); } } @Override protected void validateAndSetFeature(Feature feature, Object value) { switch (feature) { case SUPPORT_UNKNOWN_FIELDS: case CLOSE_STREAM: if (!(Boolean.TRUE.equals(value) || Boolean.FALSE.equals(value))) { throw new IllegalArgumentException(String.format("Unsupported value [%s] for feature [%s]", value, feature)); } break; case UNKNOWN_FIELD_ELEM_NAME: if (value == null || !(String.class).isAssignableFrom(value.getClass()) || ((String) value).trim().equals("")) { throw new IllegalArgumentException(String.format("Feature [%s] expected to be a non null string", feature)); } break; case FIELD_NAME_READ_SUBSTITUTES: case FIELD_NAME_WRITE_SUBSTITUTES: case STRIP_FIELD_NAME_UNDERSCORES: //noop Already handled break; default: throw new IllegalArgumentException(String.format("Unsupported feature [%s]", feature)); } } /** * Returns the element name to be used for the root element. * @param message * @return message.getClass().getSimpleName().toLowerCase() */ protected static final String getRootElementName(Message message) { return message.getClass().getSimpleName().toLowerCase(); } /** * @param builder * @return * @see XmlCodec#getRootElementName(Message) */ protected static final String getRootElementName(Builder builder) { return getRootElementName(builder.getDefaultInstanceForType()); } }