The answer that follows assumes that the phrase "java custom control" in your question refers to a JSF component you developed; in XPages, the term "custom control" usually refers to an instance of a Custom Control design element, which is IBM's implementation of the JSF notion of "composite components".
You've stated that the component initially behaves as intended but fails on subsequent requests. This typically indicates that the restoreState
and saveState
methods of the component have not been properly implemented.
When the default serialization options are enabled for an application, all component state is written to disk at the end of each request, and read back into memory at the beginning of the next. These two operations are handled, respectively, by the saveState
and restoreState
methods of each component.
For example, suppose you defined a component for adding HTML canvas tags to an XPage, and decided to support the gesture and touch events associated with that element. So your component class would contain fields to store any code bound to those events:
private String ongesturechange;
private String ongestureend;
private String ongesturestart;
private String ontouchcancel;
private String ontouchend;
private String ontouchmove;
private String ontouchstart;
Each of those fields would typically then have an associated "getter" and "setter" method:
public String getOngesturechange() {
return getStringProperty("ongesturechange", this.ongesturechange);
}
public void setOngesturechange(String ongesturechange) {
this.ongesturechange = ongesturechange;
}
When an instance of that component is initialized, the "setter" method associated with each attribute that is defined for that component instance will be passed the value defined for that attribute. For the remainder of the initial page request, then, the private field for each defined attribute will store the value that was set. At the end of the request, the saveState
method writes the values of these fields to disk. A typical saveState
method looks similar to the following:
@Override
public Object saveState(FacesContext context) {
Object[] properties = new Object[8];
int idx = 0;
properties[idx++] = super.saveState(context);
properties[idx++] = this.ongesturechange;
properties[idx++] = this.ongestureend;
properties[idx++] = this.ongesturestart;
properties[idx++] = this.ontouchcancel;
properties[idx++] = this.ontouchend;
properties[idx++] = this.ontouchmove;
properties[idx++] = this.ontouchstart;
return properties;
}
The call to super.saveState()
executes the same method, but using the version of the method defined in the parent class. So the on-disk representation of each component is essentially a nested array: each layer in the hierarchy stores all the properties it inherits from its parent class in the first element of the array, then stores all the properties that it defines in additional array elements.
When the component tree is restored on subsequent requests, each component uses its restoreState
method to reconstitute the values of all its fields. A typical restoreState
method looks similar to the following:
@Override
public void restoreState(FacesContext context, Object state) {
Object[] properties = (Object[]) state;
int idx = 0;
super.restoreState(context, properties[idx++]);
this.ongesturechange = ((String) properties[idx++]);
this.ongestureend = ((String) properties[idx++]);
this.ongesturestart = ((String) properties[idx++]);
this.ontouchcancel = ((String) properties[idx++]);
this.ontouchend = ((String) properties[idx++]);
this.ontouchmove = ((String) properties[idx++]);
this.ontouchstart = ((String) properties[idx++]);
}
This hierarchically reads the on-disk data back in: each class passes a set of properties to the parent class, then assigns the remaining array elements to the fields they were associated with when the component state was saved.
This process provides an easy way to maintain component state across requests -- each layer of inheritance need only concern itself with the new properties that layer defines -- but these state maintenance methods are easy to forget to implement. If either method is omitted from the component implementation, then the page "forgets" the property values on subsequent requests, because either they were never written to disk, or were not loaded back into memory, or both.
Assuming that this is the root cause of your problem, the reason the problem does not occur when the component is inside a dialog with the default (false
) value for keepComponents
is because the default dialog behavior is to remove its children from the component tree entirely when the dialog is closed. This behavior is for performance reasons: there's theoretically no benefit to be gained from storing a server-side representation of components that only exist inside a dialog that the user is not currently interacting with. When the dialog is opened again, a new instance of each child component is created using the original property values. In this scenario, it wouldn't matter that your component isn't saving its state, because each time it's used, a new instance is created. But if the dialog is told to keep its children in the component tree, now the component must properly maintain its own state... otherwise its property values are discarded at the end of each request and subsequent requests are unaware of the previous values.
In summary, yes, the data you're displaying should be cached in a bean (or data source) if the data is unlikely to change enough between requests to justify obtaining the data again during every single event. But the reason for the specific behavior you're describing is most likely because your component implementation is not properly maintaining its own state.