/* * ==================== * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of the Common Development * and Distribution License("CDDL") (the "License"). You may not use this file * except in compliance with the License. * * You can obtain a copy of the License at * http://opensource.org/licenses/cddl1.php * See the License for the specific language governing permissions and limitations * under the License. * * When distributing the Covered Code, include this CDDL Header Notice in each file * and include the License file at http://opensource.org/licenses/cddl1.php. * If applicable, add the following below this CDDL Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== */ package org.identityconnectors.framework.impl.serializer.xml; import java.lang.reflect.Array; import java.util.Stack; import org.identityconnectors.common.Assertions; import org.identityconnectors.common.Base64; import org.identityconnectors.common.XmlUtil; import org.identityconnectors.framework.common.exceptions.ConnectorException; import org.identityconnectors.framework.impl.serializer.ObjectEncoder; import org.identityconnectors.framework.impl.serializer.ObjectSerializationHandler; import org.identityconnectors.framework.impl.serializer.ObjectSerializerRegistry; import org.identityconnectors.framework.impl.serializer.ObjectTypeMapper; public class XmlObjectEncoder implements ObjectEncoder { private static class OutputElement { private final String name; private final StringBuilder contents = new StringBuilder(); private boolean elementData = false; public OutputElement(String name) { this.name = name; } } private Stack<OutputElement> outputStack = new Stack<OutputElement>(); private StringBuilder rootBuilder; public XmlObjectEncoder(StringBuilder builder) { Assertions.nullCheck(builder, "builder"); rootBuilder = builder; } public String writeObject(Object o) { return writeObjectInternal(o, false); } public void writeBooleanContents(boolean v) { writeStringContentsInternal(encodeBoolean(v)); } public void writeBooleanField(String fieldName, boolean v) { writeAttributeInternal(fieldName, encodeBoolean(v)); } public void writeByteContents(byte v) { writeStringContentsInternal(encodeByte(v)); } public void writeByteArrayContents(byte[] v) { writeStringContentsInternal(encodeByteArray(v)); } public void writeClassContents(Class<?> v) { writeStringContentsInternal(encodeClass(v)); } public void writeClassField(String name, Class<?> v) { if (v != null) { writeAttributeInternal(name, encodeClass(v)); } } public void writeDoubleContents(double v) { writeStringContentsInternal(encodeDouble(v)); } public void writeDoubleField(String fieldName, double v) { writeAttributeInternal(fieldName, encodeDouble(v)); } public void writeFloatContents(float v) { writeStringContentsInternal(encodeFloat(v)); } public void writeFloatField(String fieldName, float v) { writeAttributeInternal(fieldName, encodeFloat(v)); } public void writeIntContents(int v) { writeStringContentsInternal(encodeInt(v)); } public void writeIntField(String fieldName, int v) { writeAttributeInternal(fieldName, encodeInt(v)); } public void writeLongContents(long v) { writeStringContentsInternal(encodeLong(v)); } public void writeLongField(String fieldName, long v) { writeAttributeInternal(fieldName, encodeLong(v)); } public void writeObjectContents(Object o) { if (outputStack.size() == 0) { throw new IllegalStateException("May not write contents on top-level object"); } writeObjectInternal(o, false); } public void writeObjectField(String fieldName, Object object, boolean inline) { if (outputStack.size() == 0) { throw new IllegalStateException("May not write field on top-level object"); } if (inline && object == null) { return; // don't write anything } beginElement(fieldName); writeObjectInternal(object, inline); endElement(); } public void writeStringContents(String str) { writeStringContentsInternal(str); } public void writeStringField(String fieldName, String str) { if (str != null) { writeAttributeInternal(fieldName, str); } } static String encodeBoolean(boolean b) { return String.valueOf(b); } static String encodeByte(byte singleByte) { return Byte.toString(singleByte); } private static String encodeByteArray(byte[] bytes) { return Base64.encode(bytes); } private static String encodeClass(Class<?> clazz) { ObjectSerializationHandler handler = ObjectSerializerRegistry.getHandlerByObjectType(clazz); ObjectTypeMapper mapper = ObjectSerializerRegistry.getMapperByObjectType(clazz); if (handler == null && clazz.isArray()) { // we may have special handlers for certain types of arrays // if handler is null, treat like any other array return encodeClass(clazz.getComponentType()) + "[]"; } else if (mapper == null) { throw new ConnectorException("No serializer for class: " + clazz); } else { String typeName = mapper.getHandledSerialType(); return typeName; } } static String encodeDouble(double d) { return String.valueOf(d); } static String encodeFloat(float d) { return String.valueOf(d); } static String encodeInt(int d) { return String.valueOf(d); } static String encodeLong(long d) { return String.valueOf(d); } /** * Writes the object * * @param object * @param inline * @return The type name (regardless of whether it was inlined) */ String writeObjectInternal(Object object, boolean inline) { if (object == null) { if (inline) { throw new IllegalArgumentException("null cannot be inlined"); } beginElement("null"); endElement(); return "null"; } else { Class<?> clazz = object.getClass(); ObjectSerializationHandler handler = ObjectSerializerRegistry.getHandlerByObjectType(clazz); if (handler == null) { // we may have special handlers for certain types of arrays // if handler is null, treat like any other array if (clazz.isArray()) { if (!inline) { String componentTypeName = encodeClass(clazz.getComponentType()); beginElement("Array"); writeAttributeInternal("componentType", componentTypeName); } int length = Array.getLength(object); for (int i = 0; i < length; i++) { Object val = Array.get(object, i); writeObjectInternal(val, false); } if (!inline) { endElement(); } return "Array"; } else { throw new ConnectorException("No serializer for class: " + clazz); } } else { String typeName = encodeClass(clazz); if (!inline) { beginElement(typeName); } handler.serialize(object, this); if (!inline) { endElement(); } return typeName; } } } // //////////////////////////////////////////////////////////////// // // xml encoding // // /////////////////////////////////////////////////////////////// private OutputElement getCurrentElement() { if (outputStack.size() == 0) { return null; } else { return outputStack.peek(); } } private StringBuilder getCurrentBuilder() { if (outputStack.size() == 0) { return rootBuilder; } else { return getCurrentElement().contents; } } private StringBuilder getPreviousBuilder() { if (outputStack.size() == 0) { return null; } else if (outputStack.size() == 1) { return rootBuilder; } else { return outputStack.get(outputStack.size() - 2).contents; } } private void beginElement(String name) { indent(getCurrentBuilder(), outputStack.size()); OutputElement current = getCurrentElement(); if (current != null) { current.elementData = true; } getCurrentBuilder().append("<" + name); outputStack.push(new OutputElement(name)); } private void endElement() { OutputElement endedElement = outputStack.pop(); String contents = endedElement.contents.toString(); StringBuilder currentBuilder = getCurrentBuilder(); if (contents.length() == 0) { currentBuilder.append("/>\n"); // empty element } else { currentBuilder.append(">"); if (endedElement.elementData) { currentBuilder.append("\n"); } getCurrentBuilder().append(contents); if (endedElement.elementData) { indent(currentBuilder, outputStack.size()); } currentBuilder.append("</").append(endedElement.name).append(">\n"); } } private void writeAttributeInternal(String fieldName, String str) { StringBuilder previousBuilder = getPreviousBuilder(); previousBuilder.append(" ").append(fieldName).append("='"); XmlUtil.escape(previousBuilder, str, XmlUtil.SINGLE_QUOTE); previousBuilder.append("'"); } private void writeStringContentsInternal(String str) { StringBuilder builder = getCurrentBuilder(); XmlUtil.escape(builder, str, XmlUtil.NO_DELIM); } private void indent(StringBuilder builder, int level) { for (int i = 0; i < level * 2; i++) { builder.append(" "); } } }