/*
* 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);
}
}