Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove the dependency on a Java enum's values?

[Mind the gap: I know that the best solution would be to get rid of the enum completely, but that's not an option for today as mentioned in the comments, but it is planned for the (far) future.]

We have two deployment units: frontend and backend. The frontend uses an enum and calls an EJB service at the backend with the enum as a parameter. But the enum changes frequently, so we don't want the backend to know its values.

String constants

A possible solution would be to use String constants insteadof enums, but that would cause a lot of little changes at the frontend. I'm searching a solution, which causes as few changes as possible in the frontend.

Wrapper class

Another solution is the usage of a wrapper class with the same interface as an enum. The enum becomes an wrapper class and the enum values become constants within that wrapper. I had to write some deserialization code to ensure object identity (as enums do), but I don't know if it is a correct solution. What if different classloaders are used? The wrapper class will implement a Java interface, which will replace the enum in the backend. But will the deserialiaztion code execute in the backend even so?

Example for a wrapper class:

public class Locomotion implements Serializable {
    private static final long serialVersionUID = -6359307469030924650L;

    public static final List<Locomotion> list = new ArrayList<Locomotion>();

    public static final Locomotion CAR = createValue(4654L);
    public static final Locomotion CYCLE = createValue(34235656L);
    public static final Locomotion FEET = createValue(87687L);

    public static final Locomotion createValue(long type) {
        Locomotion enumValue = new Locomotion(type);
        list.add(enumValue);
        return enumValue;
    }

    private final long ppId;

    private Locomotion(long type) {
        this.ppId = type;
    }

    private Object readResolve() throws ObjectStreamException {
        for (Locomotion enumValue : list) {
            if (this.equals(enumValue)) {
                return enumValue;
            }
        }
        throw new InvalidObjectException("Unknown enum value '" + ppId + "'");
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (int) (ppId ^ (ppId >>> 32));
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof Locomotion)) {
            return false;
        }
        Locomotion other = (Locomotion) obj;
        if (ppId != other.ppId) {
            return false;
        }
        return true;
    }
}

Did you already had the same problem? How did you solved it?

like image 595
Christian Strempfer Avatar asked Oct 25 '22 15:10

Christian Strempfer


2 Answers

Ok, let me see if I understand. You said that

"The frontend uses an enum and calls an EJB service at the backend with the enum as a parameter. But the enum changes frequently, so we don't want the backend to know its values"

When you say "values" I assume you are referring to the numeric value you pass in the enum constructor and not to the enum constants themselves.

Therefore, this implies that the frontend and the backend will have two different versions of the enum class, but the enum constants in them will be the same.

I am only assuming the communication is via RMI (but this is not entirely clear in your post).

Now, serialization/deserialization of enums works different than with other objects. According to the Java Serialization Specification, when a enum is serialized, only its name is serialized. And when it is deserialized, it is built using the Enum.valueOf(name) method.

So, your original wrapper proposal would not work, because the server, due to stipulated serialization of Enums will never know the actual value of the enums in the client.

Bottom line, if you intend to pass an enum to the server there is no possible way to do what you pretend to do because the values in the frontend will never reach the backend if serialization is implied.

If RMI is implied, a good solution would be to use code mobility, this way you could place the problematic class in a repository accessible to both, server and client, and when the frontend developers change the class definition, you can publish the class in the repository and the server can get it from there.

See this article about dynamic code downloading using code base property in RMI http://download.oracle.com/javase/6/docs/technotes/guides/rmi/codebase.html

Another possible solution is that you could stop using a Java Enum and use Java class with final constants, as we used to do in the old days before enums, and that way you can ensure that its values will be properly serialized when they are are sent to the backend.

Somewhat like this

public class Fruit implements Serializable{

    private static final long serialVersionUID = 1L;
    public final Fruit ORANGE = new Fruit("orange");
    public final Fruit LEMON = new Fruit("lemon");

    private String name;

    private Fruit(String name){
        this.name = name;
    }
}

This way you can be in full control of what happens upon deserialization and your wrapper pattern might work this way.

This type of construction cannot substitute an enum completely, for instance, it cannot be used in switch statements. But, if this is an issue, you could use this object as the parameter sent to the server, and let the server rebuild the enum out of it with its version of the enum class.

Your enum, therefore, could have two new methods, one to build Java instances out of the enum itself:

public static Fruit toFruit(FruitEnum enum);
public FruitEnum valueOf(Fruit fruit);

And you can use those to convert back and forth versions of the parameter for the server.

like image 110
Edwin Dalorzo Avatar answered Oct 27 '22 09:10

Edwin Dalorzo


It's an odd request, as i would think the server should know about the values of what is going into the database, but ok, i'll play along. Perhaps you could do this

public enum Giant {Fee, Fi, Fo, Fum};

public void client() {
    Giant giant = Giant.Fee;
    server(giant);
}

public void server(Enum e) {
    String valueForDB = e.name();
        //or perhaps
    String valueForDB = e.toString();
}
like image 29
MeBigFatGuy Avatar answered Oct 27 '22 09:10

MeBigFatGuy