This is the third article related to wrapping third party jars into OSGi plugins. I have meant to write this article for a long time but.... - well, now was the time for it.
The previous articles in this series are:
Depending on the jars you want to use in your application you may run into problems with permissions. This is part of Java's security model - which is different from what we are used to in the Domino world. We have ways to control it via the java.policy file in the jvm (or better use the java.pol file as I have written about before). However, this requires changing files in the filesystem - and in many cases these changes can/will be overwritten by an upgrade or even installation of fixpacks. That is not good for the stability of our application as we may not be notified of this upgrade in advance - and suddenly the application does not work.... - and the problem most likely ends up on your desk.
So what can we do to get around this and control it without having to set security parameters that are global to the entire JVM?
I had this challenge when Nathan Freemann showed me how to do this in a way that is entirely controlled within my application - which I by far prefer
.
Basically, you need to create a wrapper to the third party code so that you can wrap the privileged calls to allow "elevation" without having to use the permissions in the java.policy file. First you create the wrapper class - in this case I call it "GsonWrapper". I also added a constructor with the possibility to use another date format - as I sometimes have different requirements based on the client (especially native vs. mobile web).
public class GsonWrapper {
private static final String DFT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
private Gson gson = null;/* Constructors... */
public GsonWrapper() {
this(DFT_DATE_FORMAT);
}
public GsonWrapper(final String dateFormat) {
gson = new GsonBuilder().setDateFormat(dateFormat).create();
}
}
Now to the most interesting part. The way we can make a privileged call is by using an AccessController:
public String toJson(final Object obj) throws Exception {
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
GsonWrapper.this.toJsonImpl(obj);
return null;
}
});
return result;
}
... which requires us to implement a method that we can call from within the doPrivileged block to do the actual privileged call to the underlying jar:
public void toJsonImpl(Object obj) {
result = gson.toJson(obj);
}
The result is stored in an instance variable ("result") that builds the "gap" between the implementation method and the wrapper. Sort of the same way you have to do it if you dynamically build and execute your own LotusScript code using "Execute" and want some data back 😊
And that is it!
You are now ready to use the wrapper and that could look like this:
// Requires Gson JAR to be put in the jvm ext library... - and AllPermissions set in java.pol/java.policy !!!!
Gson json = new GsonBuilder().setDateFormat(ISO_DATETIME_FORMAT).create(); // 1
// ... alernatively wrapping it in an OSGi plugin (with AccessController.doPrivileged(....))
GsonWrapper json = new GsonWrapper(ISO_DATETIME_FORMAT); // 2
out.write(json.toJson(data));
In this simple example you need to either use the first declaration of "json" (marked "1") OR the second (marked "2"). The code obviously will not compile as it is now 😉
The way I have built this you can also see that only the declaration determines if we use the wrapper. I have kept the same method and arguments as in the underlying jar - which makes the code less dependent on the wrapper.
The "data" variable can be ANY Java object structure (single object, list/array of objects, etc) - and it all transforms nicely to the equivalent JSON structure. Very cool!
The only issue I see with this method is that you will have to create all the methods in the wrapper that you potentially need from the underlying jar - with each of the method signatures (argument lists) that you want to use.
Happy coding!
Entire source code for the wrapper (including toJson and fromJson):
package dk.dalsgaarddata.ext.gson;
import java.lang.reflect.Type;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* @author jda
* @version 1.0.0
*
* Date: 2015.09.20
*
* This utility class wraps a call to Gson into a privileged call to allow elevation without having
* to grant "allaccess" to all jars in the jvm/lib/ext
*
* This class needs to be exported to a jar file and placed in the jvm/lib/ext library for it to be able handle
* the priviledged calls - or I can be included in the OSGi plugin :-)
*/
public class GsonWrapper {
private static final String DFT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
private Gson gson = null;
private String result = null;
private Object userClass = null;
/* Constructors... */
public GsonWrapper() {
this(DFT_DATE_FORMAT);
}
public GsonWrapper(final String dateFormat) {
gson = new GsonBuilder().setDateFormat(dateFormat).create();
}
public void toJsonImpl(Object obj) {
result = gson.toJson(obj);
}
public void fromJsonImpl(String obj, Class<?> cls) {
userClass = gson.fromJson(obj, cls);
}
public void fromJsonImpl(String obj, Type type) {
userClass = gson.fromJson(obj, type);
}
@SuppressWarnings( { "unchecked" })
public String toJson(final Object obj) throws Exception {
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
GsonWrapper.this.toJsonImpl(obj);
return null;
}
});
return result;
}
@SuppressWarnings( { "unchecked" })
public Object fromJson(final String obj, final Class cls) throws Exception {
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
GsonWrapper.this.fromJsonImpl(obj, cls);
return null;
}
});
return userClass;
}
@SuppressWarnings("unchecked")
public Object fromJson(final String obj, final Type type) throws Exception {
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
GsonWrapper.this.fromJsonImpl(obj, type);
return null;
}
});
return userClass;
}
}