Quick answer: don't ever, ever, ever, for any reason whatsoever, store a handle to a Domino object for any longer than a single HTTP request. :)
I suspect that, because a Domino session is essentially a singleton, storing it in application scope was preventing it from being recycled at the end of each request, as it normally would. Under normal circumstances, any Domino object the XPage engine is aware of at the end of any HTTP request is automatically recycled. So in order to prevent the error you're receiving, it's best practice to not store handles in any scope higher than request.
The good news is, at least for the current Session
and Database
, you never need to: you can ask the variable resolver for it.
SSJS has intrinsic access to the variable resolver, since all SSJS code has to be evaluated at runtime; so any reference to session
, for example, has to ask the variable resolver what "session" currently evaluates to. This happens automatically when the entire expression is evaluated, but we can manually access the variable resolver from our own Java code:
FacesContext context = FacesContext.getCurrentInstance();
Application app = context.getApplication();
VariableResolver resolver = app.getVariableResolver();
Session currentSession = (Session) resolver.resolveVariable("session", context);
That's a lot of nonsense to type each time we want to get a handle on a variable, and resolving contextual variables is often useful (because this works for any variables that would also be valid within SSJS, not just for Domino objects), so I recommend wrapping this in a static utility method:
public class VariableUtils {
public static Object getVariableValue(String variable) {
FacesContext context = FacesContext.getCurrentInstance();
Application app = context.getApplication();
VariableResolver resolver = app.getVariableResolver();
return resolver.resolveVariable(variable, context);
}
}
Then you can easily resolve any variable from any of your Java code:
Session currentSession = (Session) VariableUtils.resolveVariable("session");
Database currentDatabase = (Database) VariableUtils.resolveVariable("database");
DominoDocument currentDocument = (DominoDocument) VariableUtils.resolveVariable("currentDocument");
If your app loads the Extension Library, there's already a static method available for those first two:
Session currentSession = (Session) ExtLibUtils.getCurrentSession();
Database currentDatabase = (Database) ExtLibUtils.getCurrentDatabase();
That class has a bunch of useful variable resolution methods you might want to check out. But having your own convenience method for resolving variables is useful for any contextual inspection -- for instance, retrieving the current row for a view panel, data table, or repeat; the query string parameter map (param
); any data source. Just like SSJS, your Java code is typically triggered within the context of a given component (for instance, the onclick
event handler of a button), so any variables that are valid for that component can be resolved this way.
One last note about storage of Domino objects: just store metadata instead. So, if you would otherwise be storing a database (other than the current), store instead its filepath or replica ID, and when you need to access it, use the stored metadata to ask the current session for a handle to the database. Similarly, store the name of a view, but not the View
itself; store a document's NoteID or UNID, but not the actual Document
. If you find you're having to obtain these handles repeatedly, and therefore wishing they were cached, revisit the way your logic is structured... the code should be refactored to establish a handle once, do whatever needs to be done with / to that object, then discard it (and, if it's not a session, database, or an object that you also have a data source bound to, manually recycle it).
Keep in mind that any Java object that implements Serializable
and does not store pointers to any Domino objects can be stored as long as you want. So create "model" objects for your data (and populate their properties by reading the corresponding Domino handles), store those objects in scope, and then write back to the corresponding Domino objects whenever appropriate. If IBM hadn't tried to simplify XPages for us by creating the document and view data sources for us, this is what we'd already be doing (for instance, we'd be binding all of our editable fields to properties of bean classes like Contact
, ExpenseReport
, Facility
, etc., not directly to note items). But because these data sources do exist, we're still thinking in terms of "documents" and "views", instead of thinking in terms of the people, physical objects, and business processes that the data represents. As a result, we maintain unnecessary links between our user interfaces and back end data, instead of only touching the back end data when we need to do an initial query and an eventual update (or create). Sorry to get so philosophical, but so much of what we try to do in XPages gets so much easier when we transition beyond thinking in documents. :)
new MyClass(sessionAsSigner)
. In my sample I don't even keep reference to it. I just callnew MyClass(sessionAsSigner).getStuff()
and whengetStuff()
is called thesession
is already recycled! The session is stored in the parent object of the object I'm using. – Panu HaaramoisRecycled()
and re-get it but I don't understand why I have to do that. And the same applies to all Domino objects. In some cases it's costly to re-get them (f.e. DocumentCollection). As said the problem is solved by using "Keep pages in memory" but of course I'd like my code to work also with other persistence settings. – Panu Haaramo