/* * ==================== * 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.binary; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; import org.identityconnectors.framework.common.exceptions.ConnectorException; import org.identityconnectors.framework.common.serializer.BinaryObjectDeserializer; import org.identityconnectors.framework.impl.serializer.ObjectDecoder; import org.identityconnectors.framework.impl.serializer.ObjectSerializationHandler; import org.identityconnectors.framework.impl.serializer.ObjectSerializerRegistry; import org.identityconnectors.framework.impl.serializer.ObjectTypeMapper; public class BinaryObjectDecoder implements ObjectDecoder, BinaryObjectDeserializer { private static class ReadState { public Map<String, byte[]> objectFields = new HashMap<String, byte[]>(); public List<byte[]> anonymousFields = new ArrayList<byte[]>(); public DataInputStream currentInput; public ReadState() { } public boolean startField(String name) { currentInput = null; byte[] content = objectFields.get(name); if (content == null) { return false; } else { currentInput = new DataInputStream(new ByteArrayInputStream(content)); return true; } } public void startAnonymousField(int index) { if (index >= anonymousFields.size()) { throw new ConnectorException("Anonymous content not found"); } currentInput = new DataInputStream(new ByteArrayInputStream(anonymousFields.get(index))); } } private static class InternalDecoder { private boolean firstObject = true; private final Map<Integer, String> constantPool = new HashMap<Integer, String>(); private final Stack<ReadState> readStateStack = new Stack<ReadState>(); private final DataInputStream rootInput; public InternalDecoder(DataInputStream input) { rootInput = input; } public Object readObject(ObjectDecoder decoder) { if (firstObject) { int magic = readInt(); if (magic != BinaryObjectEncoder.OBJECT_MAGIC) { throw new ConnectorException("Bad magic number: " + magic); } int version = readInt(); if (version != BinaryObjectEncoder.ENCODING_VERSION) { throw new ConnectorException("Unexpected version: " + version); } firstObject = false; } // if it's a top-level object, it's proceeded by a constant pool if (readStateStack.size() == 0) { int size = readInt(); for (int i = 0; i < size; i++) { String constant = readString(false); int code = readInt(); constantPool.put(code, constant); } } Class<?> clazz = readClass(); ReadState state = new ReadState(); while (true) { byte type = readByte(); if (type == BinaryObjectEncoder.FIELD_TYPE_END_OBJECT) { break; } else if (type == BinaryObjectEncoder.FIELD_TYPE_ANONYMOUS_FIELD) { byte[] bytes = readByteArray(); state.anonymousFields.add(bytes); } else if (type == BinaryObjectEncoder.FIELD_TYPE_NAMED_FIELD) { String fieldName = readString(true); byte[] bytes = readByteArray(); state.objectFields.put(fieldName, bytes); } else { throw new ConnectorException("Unknown type: " + type); } } // push the state on the stack before we read the body readStateStack.push(state); Object rv; if (clazz == null) { rv = null; } else { 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()) { int length = getNumAnonymousFields(); Object array = Array.newInstance(clazz.getComponentType(), length); for (int i = 0; i < length; i++) { startAnonymousField(i); Object element = readObject(decoder); Array.set(array, i, element); } rv = array; } else { throw new ConnectorException("No deserializer for type: " + clazz); } } else { rv = handler.deserialize(decoder); } } // pop readStateStack.pop(); return rv; } public Class<?> readClass() { int type = readByte(); if (type == BinaryObjectEncoder.OBJECT_TYPE_NULL) { return null; } else if (type == BinaryObjectEncoder.OBJECT_TYPE_ARRAY) { Class<?> componentClass = readClass(); return Array.newInstance(componentClass, 0).getClass(); } else if (type == BinaryObjectEncoder.OBJECT_TYPE_CLASS) { String typeName = readString(true); ObjectTypeMapper mapper = ObjectSerializerRegistry.getMapperBySerialType(typeName); if (mapper == null) { throw new ConnectorException("No deserializer for type: " + typeName); } return mapper.getHandledObjectType(); } else { throw new ConnectorException("Bad type value: " + type); } } public int getNumAnonymousFields() { ReadState readState = readStateStack.get(readStateStack.size() - 1); return readState.anonymousFields.size(); } public void startAnonymousField(int index) { ReadState readState = readStateStack.get(readStateStack.size() - 1); readState.startAnonymousField(index); } public boolean startField(String name) { ReadState readState = readStateStack.get(readStateStack.size() - 1); return readState.startField(name); } public int readInt() { try { return getCurrentInput().readInt(); } catch (IOException e) { throw ConnectorException.wrap(e); } } public long readLong() { try { return getCurrentInput().readLong(); } catch (IOException e) { throw ConnectorException.wrap(e); } } public double readDouble() { try { return getCurrentInput().readDouble(); } catch (IOException e) { throw ConnectorException.wrap(e); } } public byte[] readByteArray() { try { int length = getCurrentInput().readInt(); byte[] rv = new byte[length]; getCurrentInput().readFully(rv); return rv; } catch (IOException e) { throw ConnectorException.wrap(e); } } public byte readByte() { try { return getCurrentInput().readByte(); } catch (IOException e) { throw ConnectorException.wrap(e); } } public boolean readBoolean() { try { return getCurrentInput().readBoolean(); } catch (IOException e) { throw ConnectorException.wrap(e); } } public String readString(boolean interned) { if (interned) { int code = readInt(); String name = constantPool.get(code); if (name == null) { throw new ConnectorException("Undeclared code: " + code); } return name; } try { byte[] bytes = readByteArray(); return new String(bytes, "UTF8"); } catch (IOException e) { throw ConnectorException.wrap(e); } } private DataInputStream getCurrentInput() { if (readStateStack.size() > 0) { ReadState state = readStateStack.get(readStateStack.size() - 1); return state.currentInput; } else { return rootInput; } } } private InternalDecoder internalDecoder; public BinaryObjectDecoder(InputStream in) { internalDecoder = new InternalDecoder(new DataInputStream(new BufferedInputStream(in, 4096))); } public void close() { try { internalDecoder.rootInput.close(); } catch (IOException e) { throw ConnectorException.wrap(e); } } public Object readObject() { return internalDecoder.readObject(this); } public boolean readBooleanContents() { internalDecoder.startAnonymousField(0); return internalDecoder.readBoolean(); } public boolean readBooleanField(String fieldName, boolean dflt) { if (internalDecoder.startField(fieldName)) { return internalDecoder.readBoolean(); } else { return dflt; } } public byte readByteContents() { internalDecoder.startAnonymousField(0); return internalDecoder.readByte(); } public byte[] readByteArrayContents() { internalDecoder.startAnonymousField(0); return internalDecoder.readByteArray(); } public Class<?> readClassContents() { internalDecoder.startAnonymousField(0); return internalDecoder.readClass(); } public Class<?> readClassField(String fieldName, Class<?> dflt) { if (internalDecoder.startField(fieldName)) { return internalDecoder.readClass(); } else { return dflt; } } public double readDoubleContents() { internalDecoder.startAnonymousField(0); return internalDecoder.readDouble(); } public double readDoubleField(String fieldName, double dflt) { if (internalDecoder.startField(fieldName)) { return internalDecoder.readDouble(); } else { return dflt; } } public float readFloatContents() { internalDecoder.startAnonymousField(0); // read as double since C# only knows how to deal with that return (float) internalDecoder.readDouble(); } public float readFloatField(String fieldName, float dflt) { if (internalDecoder.startField(fieldName)) { return (float) internalDecoder.readDouble(); } else { return dflt; } } public int readIntContents() { internalDecoder.startAnonymousField(0); return internalDecoder.readInt(); } public int readIntField(String fieldName, int dflt) { if (internalDecoder.startField(fieldName)) { return internalDecoder.readInt(); } else { return dflt; } } public long readLongContents() { internalDecoder.startAnonymousField(0); return internalDecoder.readLong(); } public long readLongField(String fieldName, long dflt) { if (internalDecoder.startField(fieldName)) { return internalDecoder.readLong(); } else { return dflt; } } public int getNumSubObjects() { return internalDecoder.getNumAnonymousFields(); } public Object readObjectContents(int index) { internalDecoder.startAnonymousField(index); return internalDecoder.readObject(this); } public Object readObjectField(String fieldName, Class<?> expected, Object dflt) { if (internalDecoder.startField(fieldName)) { return readObject(); } else { return dflt; } } public String readStringContents() { internalDecoder.startAnonymousField(0); return internalDecoder.readString(false); } public String readStringField(String fieldName, String dflt) { if (internalDecoder.startField(fieldName)) { return internalDecoder.readString(false); } else { return dflt; } } }