Tuesday, July 12, 2011

Howto store java.lang.Class objects in db4o

I ran into an interesting problem when querying our db4o NoSQL database the other day. I had an object with a java.lang.Class as one of it's fields. Every time i queried for this object the Class field was set to null. Below is an example of what my model class looked like:
public class MessageServicePreference {
    private final UUID userId;
    private String serviceName;
    private Class<?> serviceClass;

    public MessageServicePreference(UUID userId, MessageService service) {
        this.userId = userId;
        this.serviceName = service.getName();
        this.serviceClass = service.getClass(); //gets the actual implementation class, not MessagingService.class
    }

    public UUID getUserId() {
        return userId;
    }

    public String getServiceName() {
        return serviceName;
    }

    public Class<?> getServiceClass() {
        return serviceClass;
    }

    public void setService(MessageService service) {
        serviceName = service.getName();
        serviceClass = service.getClass();
    }
}

So, like i said when i queried for this class using db4o:
public MessageServicePreference getPreferenceFor(User user) {
    MessageServicePreference preference = null;
    ObjectContainer db = DatabaseConnectionPool.aquireConnection();
    try {
        Query query = db.query();
        query.constrain(MessageServicePreference.class);
        query.descend("userId").constrain(user.getId());
        List<MessageServicePreference> preferences = query.execute();
        if (!preferences.isEmpty()) {
            preference = preferences.get(0);
        }
        return preference;
    } finally {
        DatabaseConnectionPool.releaseConnection(db);
    }
}

This query would return a MessageServicePreference, but when i called .getServiceClass() it would return null. So, by inspecting the java.lang.Class source code i found that all of the fields in the source were either static or transient, and db4o doesn't persist static or transient fields. Then it i found the method java.lang.Class.forName(String className) and came up with the following solution:

public class MessageServicePreference {
    private final UUID userId;
    private String serviceName;
    private String serviceClassName;

    public MessageServicePreference(UUID userId, MessageService service) {
        this.userId = userId;
        this.serviceName = service.getName();
        this.serviceClassName = service.getClass().getName(); //gets the actual implementation class's fully qualified name (package.to.class.MessageServiceImpl)
    }

    public UUID getUserId() {
        return userId;
    }

    public String getServiceName() {
        return serviceName;
    }

    public Class<?> getServiceClass() {
        return Class.forName(serviceClassName);
    }

    public void setService(MessageService service) {
        serviceName = service.getName();
        serviceClassName = service.getClass().getName();
    }
}

This new model class saves all of it's data properly in db4o and when queried populates the serviceClassName field correctly, which in turn makes the getServiceClass() method work as expected.

No comments:

Post a Comment