0

I would like to save the data of an injected stateful bean at various intervals: change - save - change- save... I'm using core serialization and the problem is that all the byte arrays are the same. i believe the proxy is serialized because if I deserialize one of the arrays later I get the current state of the bean.

Example of serialization not capturing changes in the bean:

@Stateful
@RequestScoped
public class State implements Serializable {

    private static final long serialVersionUID = 1L;

    @Inject
    StatelessBean bean; // assume it's needed

    private List<String> list = new ArrayList<>();

    public void add() {
        list.add("S");
    }
}

And this is a JAX-RS class:

@Stateless
@Path("t1")
public class ChickensResource {

    @Inject
    State state;

    @GET
    @Path("/test")
    public String test() {
        state.add();
        byte[] b0 = serialize(state);
        System.out.println(b0.length + " " + Arrays.toString(b0));
        state.add();
        byte[] b1 = serialize(state);
        System.out.println(b1.length + " " + Arrays.toString(b1)); // prints same as b0
        System.out.println(b0.length + " " + Arrays.toString(b0)); // prints same thing
    }

    public static <T extends Serializable> byte[] serialize(T s) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos))
        {
            oos.writeObject(s);
            return bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

What I want to do is only save the list in State as that's the relevant data. I also tried JSON serialization and it gave an IOException, but I'm trying core serialization.

Using JavaEE7 and Wildfly 10.1.

Mark
  • 2,167
  • 4
  • 32
  • 64
  • You need to change the `bean` declaration in `State` to `transient StatelessBean bean` – Steve C Aug 23 '17 at 01:10
  • 1
    Since `State` is `@RequestScoped`, the injected thing in `ChickensResource.state` is a _proxy_. That is why serialization always returns the same thing. Why don't you add a `serialize()` method to the `State` that handles the internals of its own serialization and takes care of dependencies appropriately? – Nikos Paraskevopoulos Aug 23 '17 at 12:06
  • @NikosParaskevopoulos thanks. Does this happen only with `@RequestScope`? My real `State` object holds a graph of objects, some are injected and some are not so when exactly do I need to go with your approach? Also I [read](https://stackoverflow.com/questions/13700306/best-practice-for-serialization-for-ejb-and-cdi-beans) that if the proxy of injected beans is not serialized then when deserializing them it will remain `null`. – Mark Aug 23 '17 at 16:15
  • @NikosParaskevopoulos it seems to be happening with any scope. That means that scoped beans can't be serialized? – Mark Aug 23 '17 at 17:32
  • 1
    This is supposed to happen with all "normal scopes": request, session, and application (+any custom ones). The spec speaks about *passivation*, which builds on serialization, but is a totally different thing. There is no standalone mention of serialization in the spec. I wouldn't serialize a CDI bean in any way (Java serialization, JSON, etc). What I *would* do in your place is have the CDI bean save its state in a different object that is not managed by CDI (i.e. created with `new`) and *is* serializable. – Nikos Paraskevopoulos Aug 24 '17 at 07:06
  • @NikosParaskevopoulos So when I deserialize I would set the non-managed data object in the post construct of the bean? Can you post an answer please? – Mark Aug 24 '17 at 07:35

1 Answers1

3

For various reasons, serializing a CDI bean directly is dangerous:

  • You may have a proxy, not the actual object; same holds true for the dependencies of that object
  • Serialization implies that the data will be deserialized at a time. But CDI beans are managed by CDI and CDI has no way to "attach" a deserialized object into its set of managed objects.

But the purpose of this question is to somehow save the state of a CDI bean in a way that it can be restored later. This can be accomplished by using another object that holds the state of the CDI bean. This other object is not managed by CDI, i.e. created with new, and is serializable. Each CDI bean that needs to persist its state has the pair of setState(state)/getState() methods - they could even be part of an interface. You probably want each object to propagate setState(state)/getState() to its collaborators too.

See the Memento design pattern. This is also implemented in the JSF state saving/restoring mechanism, if you are familiar with it.


Some example code (there are other valid ways to do it), starting with the state interface:

interface HasState<S extends Serializable> {
    S getState();
    void setState(S state);
}

Then the service itself, that has a collaborator, and the relevant state object:

class SomeServiceState implements Serializable {
    private String someData;
    private Long someId;
    private List<String> list;
    private CollaboratorState collaboratorState;
    // accessors
}

@RequestScoped
public class SomeService implements HasState<SomeServiceState> {

    // COLLABORATORS
    @Inject
    Collaborator collaborator; // assume it's needed

    // INTERNAL STATE
    private String someData;
    private Long someId;
    private List<String> list = new ArrayList<>();

    public void add() {
        list.add("S");
    }

    // ...

    public SomeServiceState getState() {
        SomeServiceState state = new SomeServiceState();
        state.setSomeData(someData);
        state.setSomeId(someId);
        state.setList(new ArrayList<>(list)); // IT IS PROBABLY SAFER TO COPY STATE!
        // SEE HOW STATE GETS EXTRACTED RECURSIVELY:
        state.setCollaboratorState(collaborator.getState());
        return state;
    }

    public void setState(SomeServiceState state) {
        someData = state.getSomeData();
        someId = state.getSomeId();
        list = new ArrayList<>(state.getList());
        // SEE HOW STATE GETS APPLIED RECURSIVELY:
        collaborator.setState(state.getCollaboratorState());
    }
}

The collaborator and its state follow the same pattern:

class CollaboratorState implements Serializable {
    private String anyName;
    // accessors
}

@RequestScoped
class Collaborator implements HasState<CollaboratorState> {
    // you get the point...
}

And an example usage, following the code from the question:

@Stateless
@Path("t1")
public class ChickensResource {

    @Inject
    SomeService someService;

    @GET
    @Path("/test")
    public String test() {
        someService.add();
        byte[] b0 = serialize(someService.getState());
        // ...
    }

    public static <T extends Serializable> byte[] serialize(T s) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos))
        {
            oos.writeObject(s);
            return bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

EDIT: If the client of a service needs to know that a service has state, then the client and service might be more coupled than it would be desired. A way out is to modify HasState to deal with opaque objects:

interface HasState {
    Object getState();
    void setState(Object state);
}

The state of the client contains a list for the state of each collaborator:

class SomeServiceState implements Serializable {
    private String someData;
    private Long someId;
    private List<String> list;
    private List<Object> collaboratorsState;
    // accessors
}

The client adds a collaborator to the state only if it extends HasState:

    public Object getState() {
        SomeServiceState state = new SomeServiceState();
        state.setSomeData(someData);
        state.setSomeId(someId);
        state.setList(new ArrayList<>(list));
        if( collaborator instanceof HasState ) {
            state.getCollaboratorsState().add(collaborator.getState());
        }
        return state;
    }
Nikos Paraskevopoulos
  • 39,514
  • 12
  • 85
  • 90
  • Thanks. If the Collaborator has no state, say it's an injected `@Stateless` bean, it doesn't implement the `HasState` interface and then I can just remove the line `state.setCollaboratorState(collaborator.getState());`? – Mark Aug 24 '17 at 15:57
  • Yes, of course! This creates a coupling because the client of a service has to know if that service has state or not. However there is a way out (taken from the JSF state saving mechanism). Modify `getState()`/`setState()` to return/take `Object`. Then keep the state of a collaborator in an array, only if it implements `HasState`. I will update the answer. – Nikos Paraskevopoulos Aug 24 '17 at 17:49
  • Iv'e done a lot of playing around with various designs and found this way: I mark the injected field as `transient` and serialize normally. After deser. I obtain a new instance with `Instance#get/select`, call get on the injected proxy on that instance and set it in my deserialized non-managed object. Then I call `destroy` on the managed instance. (In other words, I create a managed object just to steal its proxy.) I tested it and it works technically but I'm 99% sure this is bad for some reason. Maybe passivation? but I can opt out. – Mark Sep 01 '17 at 00:02
  • I'm not sure I fully understand what you did, but it does look like a hack. Do you mean that you need 2 fields, (1) `transient SomeService someService` and (2) `@Inject Instance someServiceInstance`? And why call `destroy()`? – Nikos Paraskevopoulos Sep 01 '17 at 21:45
  • `SomeService` has a `@Inject transient Collab collab` field. I do regular ser. and deser. on `SomeService` in another class. Now collab is `null`. In that other class I have `@Inject Instance someServiceInstance`. After deser. I do `someServiceInstance.get()`, call get for the (non-null) `collab` field and call set on the deser. instance with the value I got. Now my deser. instance has a working injected proxy for `collab`. Since I don't need the instance I for from `Instance` anymore I call `destroy` on it. – Mark Sep 02 '17 at 03:47
  • Sorry about the mess, I clarified what I'm doing in [this](https://stackoverflow.com/questions/46026384/how-can-i-tell-the-cdi-container-to-activate-a-bean) question and now I also [asked](https://stackoverflow.com/questions/46066680/how-to-alter-the-design-so-that-entities-dont-use-injections) one about the design if you could take a look. – Mark Sep 06 '17 at 04:18