Last week Christian Güdemann published new release of SmartNSF on OpenNTF that contains cool new feature that Christian tweeted before. With new CUSTOM strategy it allows direct execution
of Java code from REST API defined in router configuration. It's even better than it sounds as it initializes facesContext and XPages application if needed, so even access to beans works.
I needed to start to build new REST APIs for few databases, so I decided to test new SmartNSF option and also other available options for REST APIs on Domino (there are several, check references at the end for more info). Since CUSTOM strategy requires dependency on SmatNSF in NSF project and also implementation of CustomRestHandler interface, it'd force me to do more changes in my code that I wanted to. If I need to change my code, why not adjust it for JAX-RS spec anyway.
Existing Domino JAX-RS options had to packaged as plugins, which make it hard to call code that is currently in NSF. I could make it work using pieces from SmartNSF, more classloading hacks and java reflection, but calling all code using reflection isn't best for development productivity. This way I was able to build JAX-RS API on top of existing NSF without changing anything in NSF itself, which would be perfect for my current use case, but I didn't like it.
I decided to take different approach. Use code from SmartNSF to take care of facesContext initialization and create XSP library that would allow direct usage of JAX-RS in NSF. ExtLib already contains AbstractRestServlet that wraps Wink implementation that is available on Domino for long time, so at the end this solution required far less code than I expected.
Using com.ibm.xsp.adpater.servletFactory I was able to register own factory that takes care of new servlet creation for each servlet/nsf.
This factory also passes properties map to the servlet, so it knows where to look for Wink configuration and JAX-RS resources/providers.
Those paths are evaluated using module classloader, so it looks for files inside NSF.
CustomWinkServlet then just overrides getContextClassLoader method to point it to module classloader and takes care of facesContext and app initialization if needed.
I experienced issues with SessionAsSigner access, which I need for some configuration loading, so I had to move super.doInit() call to first doService call. Problem is that if something is loaded form NSF using module classloader when NotesContext doesn't have current session assigned or first resource is not a class (at least I think), it's not possible to get sessionAsSigner after that point. I got around by loading plugin.Activator first, which I know that should exist in any NSF.
With this infrastructure in place, wrapped as XSP library and installed on my server and Designer, I'm able to call existing XPages/Java code, including existing beans. For example:
For automated mapping of JSON data I included Jackson mapper in my plugin, so it can be just registered in wink.application along with resources.
of Java code from REST API defined in router configuration. It's even better than it sounds as it initializes facesContext and XPages application if needed, so even access to beans works.
I needed to start to build new REST APIs for few databases, so I decided to test new SmartNSF option and also other available options for REST APIs on Domino (there are several, check references at the end for more info). Since CUSTOM strategy requires dependency on SmatNSF in NSF project and also implementation of CustomRestHandler interface, it'd force me to do more changes in my code that I wanted to. If I need to change my code, why not adjust it for JAX-RS spec anyway.
Existing Domino JAX-RS options had to packaged as plugins, which make it hard to call code that is currently in NSF. I could make it work using pieces from SmartNSF, more classloading hacks and java reflection, but calling all code using reflection isn't best for development productivity. This way I was able to build JAX-RS API on top of existing NSF without changing anything in NSF itself, which would be perfect for my current use case, but I didn't like it.
I decided to take different approach. Use code from SmartNSF to take care of facesContext initialization and create XSP library that would allow direct usage of JAX-RS in NSF. ExtLib already contains AbstractRestServlet that wraps Wink implementation that is available on Domino for long time, so at the end this solution required far less code than I expected.
Using com.ibm.xsp.adpater.servletFactory I was able to register own factory that takes care of new servlet creation for each servlet/nsf.
This factory also passes properties map to the servlet, so it knows where to look for Wink configuration and JAX-RS resources/providers.
Those paths are evaluated using module classloader, so it looks for files inside NSF.
CustomWinkServlet then just overrides getContextClassLoader method to point it to module classloader and takes care of facesContext and app initialization if needed.
I experienced issues with SessionAsSigner access, which I need for some configuration loading, so I had to move super.doInit() call to first doService call. Problem is that if something is loaded form NSF using module classloader when NotesContext doesn't have current session assigned or first resource is not a class (at least I think), it's not possible to get sessionAsSigner after that point. I got around by loading plugin.Activator first, which I know that should exist in any NSF.
With this infrastructure in place, wrapped as XSP library and installed on my server and Designer, I'm able to call existing XPages/Java code, including existing beans. For example:
For automated mapping of JSON data I included Jackson mapper in my plugin, so it can be just registered in wink.application along with resources.
In wink.properties I kept wink.defaultUrisRelative=false which was mentioned od Jesse's blog, but I really don't know if it has to be there. I'll have to test it.
That's all what needs to be done. Now I can just access my new REST services: e.g.
or
If you want to try it yourself, sample database also contains showCounter.xsp that displays value from appBean, which is a proof that there is no secret double-life, which may happen when you use different approach for class loading.
Now I can just wrap existing model and controller classes with JAX-RS annotations or thin wrapper layer when needed, so I can easily use same code from XPages and REST API.
Source code is available on Bitbucket https://bitbucket.org/pradnik/pristo_rest .
I can imagine that combining this approach with OpenNTF API can make creation of JAX-RS services quite easy as ODA wrappers already take care for mapping proprietary Notes structures to standard Java classes. This is something to try next.
Happy coding
Resources
Many good articles, series, sagas and samples were published around Domino and REST topics. Here is list of those that I know of:
Paul Withers- http://www.intec.co.uk/from-xpages-to-web-app-part-sixteen-osgi-jax-rs-rest-access-with-oda-revisited/
Jesse Gallagher - https://frostillic.us/blog/posts/87267DB72A55133F85257E380073495F
Sedar Basegmex - https://github.com/sbasegmez/RestAssuredDemo
Sven
Hasselbach - http://hasselba.ch/blog/?p=2413
Toby
Samples - https://tobysamples.wordpress.com/2015/07/09/jax-rs-or-the-way-to-do-rest-in-domino-part-4/
Eric McCormick - https://edm00se.io/servlet-series/
Extension
Library REST Services - https://www.openntf.org/Projects/pmt.nsf/36B7CD129ED7357A86257AC6005523E7/$file/Extension%20Library%20REST%20Services.pdf
Comments
Post a Comment