/* * RHQ Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, as * published by the Free Software Foundation, and/or the GNU Lesser * General Public License, version 2.1, also as published by the Free * Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License and the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.rhq.core.domain.server; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.Id; import org.rhq.core.domain.resource.Agent; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.server.ExternalizableStrategy.Subsystem; /** * A utility specifically tailored to entities which will iterate over its persistence fields with a consistent * ordering for serialization and deserialization. If this class is passed a non-entity object, it will ignore * field-level annotations and just serialize and deserialize all fields in that object. * * @author Joseph Marques */ public class EntitySerializer { private static Set<Class<? extends Annotation>> PERSISTENCE_ANNOTATIONS = new HashSet<Class<? extends Annotation>>(); static { PERSISTENCE_ANNOTATIONS.add(Id.class); PERSISTENCE_ANNOTATIONS.add(Column.class); PERSISTENCE_ANNOTATIONS.add(ManyToOne.class); PERSISTENCE_ANNOTATIONS.add(OneToMany.class); PERSISTENCE_ANNOTATIONS.add(ManyToMany.class); } private static Set<Class<?>> BASIC_TYPES = new HashSet<Class<?>>(); static { BASIC_TYPES.add(Byte.TYPE); BASIC_TYPES.add(Short.TYPE); BASIC_TYPES.add(Integer.TYPE); BASIC_TYPES.add(Long.TYPE); BASIC_TYPES.add(Float.TYPE); BASIC_TYPES.add(Double.TYPE); BASIC_TYPES.add(Boolean.TYPE); } private static Comparator<Field> fieldComparator = new Comparator<Field>() { public int compare(Field first, Field second) { return first.getName().compareTo(second.getName()); } }; private static Field[] getFields(Object object) { Class<?> objectClass = object.getClass(); Entity entityAnnotation = objectClass.getAnnotation(Entity.class); List<Field> serializableFields; if (entityAnnotation == null) { serializableFields = getNonEntityFieldList(object); } else { serializableFields = getEntityFieldList(object); } Collections.sort(serializableFields, fieldComparator); Field[] results = serializableFields.toArray(new Field[serializableFields.size()]); return results; } private static List<Field> getNonEntityFieldList(Object object) { Class<?> objectClass = object.getClass(); Field[] fields = objectClass.getDeclaredFields(); List<Field> serializableFields = new ArrayList<Field>(); for (Field field : fields) { serializableFields.add(field); field.setAccessible(true); } return serializableFields; } private static List<Field> getEntityFieldList(Object entity) { Class<?> entityClass = entity.getClass(); Entity entityAnnotation = entityClass.getAnnotation(Entity.class); if (entityAnnotation == null) { throw new IllegalArgumentException("EntitySerializer only introspects objects annotated with @Entity "); } List<Field> serializableFields = new ArrayList<Field>(); while (entityClass != null) { Field[] fields = entityClass.getDeclaredFields(); for (Field field : fields) { Annotation[] fieldAnnotations = field.getAnnotations(); for (Annotation fieldAnnotation : fieldAnnotations) { if (PERSISTENCE_ANNOTATIONS.contains(fieldAnnotation.annotationType())) { serializableFields.add(field); field.setAccessible(true); break; } } } entityClass = entityClass.getSuperclass(); } return serializableFields; } public static void writeExternalRemote(Object object, ObjectOutput out) throws IOException { Field[] fields = getFields(object); for (Field field : fields) { //System.out.println("Serializing " + field.getName() + "..."); try { Class<?> type = field.getType(); Object value = field.get(object); if (BASIC_TYPES.contains(type)) { if (type.equals(Byte.TYPE)) { out.writeByte((Byte) value); } else if (type.equals(Short.TYPE)) { out.writeShort((Short) value); } else if (type.equals(Integer.TYPE)) { out.writeInt((Integer) value); } else if (type.equals(Long.TYPE)) { out.writeLong((Long) value); } else if (type.equals(Float.TYPE)) { out.writeFloat((Float) value); } else if (type.equals(Double.TYPE)) { out.writeDouble((Double) value); } else if (type.equals(Boolean.TYPE)) { out.writeBoolean((Boolean) value); } else { throw new IllegalStateException( "BASIC_TYPES contains an entry that doesn't have serialization support: " + type); } continue; } // either a string, an enum, numeric wrapper, collection, or some other object out.writeObject(value); } catch (IllegalAccessException iae) { throw new IllegalStateException("Could not access field '" + field.getName() + "' for serialization"); } } } public static void readExternalRemote(Object object, ObjectInput in) throws IOException, ClassNotFoundException { Field[] fields = getFields(object); for (Field field : fields) { //System.out.println("Deserializing " + field.getName() + "..."); try { Class<?> type = field.getType(); if (BASIC_TYPES.contains(type)) { if (type.equals(Byte.TYPE)) { field.setByte(object, in.readByte()); } else if (type.equals(Short.TYPE)) { field.setShort(object, in.readShort()); } else if (type.equals(Integer.TYPE)) { field.setInt(object, in.readInt()); } else if (type.equals(Long.TYPE)) { field.setLong(object, in.readLong()); } else if (type.equals(Float.TYPE)) { field.setFloat(object, in.readFloat()); } else if (type.equals(Double.TYPE)) { field.setDouble(object, in.readDouble()); } else if (type.equals(Boolean.TYPE)) { field.setBoolean(object, in.readBoolean()); } else { throw new IllegalStateException( "BASIC_TYPES contains an entry that doesn't have deserialization support: " + type); } continue; } // either a string, an enum, numeric wrapper, collection, or some other object field.set(object, in.readObject()); } catch (IllegalAccessException iae) { throw new IllegalStateException("Could not access field '" + field.getName() + "' for deserialization"); } } } public static void main(String[] args) throws Exception { ExternalizableStrategy.setStrategy(Subsystem.REFLECTIVE_SERIALIZATION); // create objects Agent writeAgent = new Agent("reflectiveAgent", "reflectiveAddress", 0, "reflectiveEndpoint", "reflectiveToken"); ResourceType writeResourceType = new ResourceType(); writeResourceType.setName("reflectiveType"); writeResourceType.setPlugin("reflectivePlugin"); writeResourceType.setId(7); Resource writeParentResource = new Resource(); writeParentResource.setId(11); writeParentResource.setName("reflectiveParentResource"); writeParentResource.setResourceKey("reflectiveParentKey"); Resource writeResource = new Resource(); writeResource.setId(42); writeResource.setName("reflectiveResource"); writeResource.setResourceKey("reflectiveKey"); // setup relationships writeResource.setAgent(writeAgent); writeResource.setResourceType(writeResourceType); writeResource.setParentResource(writeParentResource); System.out.println("BEFORE"); System.out.println(writeResource.toString()); System.out.println("BEFORE"); String tempDir = System.getProperty("java.io.tmpdir"); File tempFile = new File(tempDir, "entitySerializerTest.txt"); FileOutputStream fos = new FileOutputStream(tempFile); try { ObjectOutput output = new ObjectOutputStream(fos); try { writeExternalRemote(writeResource, output); } finally { output.close(); } } finally { fos.close(); } Resource readResource = new Resource(); FileInputStream fis = new FileInputStream(tempFile); try { ObjectInput ois = new ObjectInputStream(fis); try { readExternalRemote(readResource, ois); } finally { ois.close(); } } finally { fis.close(); } // quick verification System.out.println("AFTER"); System.out.println(readResource.toString()); System.out.println("AFTER"); // deeper verification boolean equalsResource = writeResource.equals(readResource); boolean equalsParentResource = writeParentResource.equals(readResource.getParentResource()); boolean equalsResourceType = writeResourceType.equals(readResource.getResourceType()); boolean equalsAgent = writeAgent.equals(readResource.getAgent()); System.out.println("equalsResource: " + equalsResource); System.out.println("equalsParentResource: " + equalsParentResource); System.out.println("equalsResourceType: " + equalsResourceType); System.out.println("equalsAgent: " + equalsAgent); } }