Posts Tagged: ‘Jackson’

Domin & REST: Debug your Plugin

7. März 2017 Posted by Sven Hasselbach

When developing OSGi Plugins, you should have your own development server running on your local machine. Not only because of the faster deployment of changes (a new version of a plugin must always deployed with a HTTP restart), but because of the Java debugging posibilities: Only one Eclipse instance can connect to the JVM, and every request processed by the server will start the debugger. If multiple users a accessing the server while you are debugging, your Eclipse will try to debug every incoming request, and this can lead into a confusing situation for all.

To enable debugging, you first have to add two parameters to the notes.ini:

JavaEnableDebug=1
JavaDebugOptions=transport=dt_socket,server=y,suspend=n,address=8000

This starts the debugging on port 8000. Feel free to change the value to every port you want. Because of security reasons you should not start debugging on a productive machine.

After restarting the domino server, you can connect to the JVM in your Eclipse IDE by creating a new JVM remote debugging session. Create a new debug configuration…

… choose (1) „Remote Java Application„, (2) give a name to it, (3) select the plugin project, (4) enter the port the server listens, and click on (5) „Apply„.

If you want to connect to your server, you need to start debugging by clicking on the project:

After setting a breakpoint and sending a request to the servlet, Eclipse switches to the Debug perspective where you can look what happens with your servlet.

Sometimes you are connecting to the „wrong“ JVM, because a Java agent is running and/or a DOTS task does it’s job. It’s better to disable these tasks on your development server.

During debugging you are able to hotswap your code, but keep in mind that after a restart of the HTTP JVM all your changes are no longer „installed“. You have to build a new plugin or replace your code during runtime again.

In the next blog post, let’s talk about our development tools.

Domino & REST: More about Jackson

3. März 2017 Posted by Sven Hasselbach

When creating a REST API servlet, Jackson provides a huge list of possibilities to manipulate the JSON data, mostly using annotations.

Let’s demonstrate some of them with this little class, which has only two properties:

public class Demo {

    private String foo;
    private String bar;

    public String getFoo() { 
        return foo;
    }
    public void setFoo(String foo) {
        this.foo = foo;
    }

    public String getBar() { 
        return bar;
    }

    public void setBar(String bar) {
        this.bar = bar;
    }
}

The playground converts the content string to a POJO and back to a string:

String content = "{ \"foo\": \"bar\" }";
 
 // init the ObjectMapper
 ObjectMapper mapper = new ObjectMapper();
 
 // build the Object
 Demo test = null;
 try {
     test = mapper.readValue(content, Demo.class);
 } catch (Exception e) {
     e.printStackTrace();
 }

 // and now convert it back to a String
 String data = null;
 try {
     data = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(test);
 } catch (Exception e) {
     e.printStackTrace();
 }

 System.out.println( data );

If we run this code, the result is not really spectacular:

{
 "foo" : "bar",
 "bar" : null
}

So let’s ignore the property foo by adding the annotation @JsonIgnoreProperties to the Demo class:

@JsonIgnoreProperties({"foo"})
public class Demo { ... }

Now, foo is no longer in our resulting JSON:

{
    "bar" : null
}

The property bar is null, and we don’t like nulled properties in our JSON. That’s why we add another annotation, @JsonInclude:

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Demo { ... }

After removing the previously added @JsonIgnoreProperties annotation, our result looks like this (the empty property bar was skipped):

{
    "foo" : "bar"
}

What happens if we change our content string, and add an unknown property?

String content = "{ \"foo\": \"bar\", \"undefined\": \"property\" }";

An error occurs because Jackson does not know how to handle the new property:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "undefined" (class ch.hasselba.JacksonPlayground.Demo), not marked as ignorable (2 known properties: "foo", "bar"])
 at [Source: { "foo": "bar", "undefined": "property" }; line: 1, column: 31] (through reference chain: ch.hasselba.JacksonPlayground.Demo["undefined"])
 at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:51)
 at com.fasterxml.jackson.databind.DeserializationContext.reportUnknownProperty(DeserializationContext.java:817)
 at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:954)
 at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1315)
 at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1293)
 at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:249)
 at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:136)
 at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3560)
 at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2576)
 at ch.hasselba.JacksonPlayground.App.main(App.java:24)
null

But there are two annotations to the rescue, @JsonAnyGetter@JsonAnySetter. By changing our Demo class and adding the following lines of code…

private Map<String, Object> others = new ConcurrentHashMap<String, Object>();

@JsonAnyGetter
public Map<String, Object> getOthers() {
    return this.others;
}

@JsonAnySetter
public void addOther(final String name, final Object value) {
    this.others.put(name, value);
}

… Jackson now puts all the unknown/undefined properties in the others map (uses the method defined by @JsonSetter). And then it uses the method with the @JsonGetter annotation when producing the JSON from the Demo instance.

{
  "foo" : "bar",
  "bar" : null,
  "undefined" : "property"
}

What if we want to handle multiple „Demo“ objects in a JSON Array?

String content = "[ { \"foo\": \"bar\" }, {\"foo\": \"bar2\" } ]";

In this case we change our reading routine to work with lists:

// build the Object
List<Demo> test = null;
try {
    test = mapper.readValue(content, mapper.getTypeFactory()
            .constructCollectionType(List.class, Demo.class));
} catch (Exception e) {
    e.printStackTrace();
}

In the result all entries are now contained in the list of Demo objects:

[ {
 "foo" : "bar",
 "bar" : null
}, {
 "foo" : "bar2",
 "bar" : null
} ]

Back to our RestApiApplication, have a look at this line:

objMapper.setSerializationInclusion(Include.NON_EMPTY);

This removes all empty properties globally from the generated output of our the servlet. So there is no need to add the @JsonIgnore annotation to any class. You can modifiy the globally used ObjectMapper in your servlet with multiple option, more will follow in another blog post.

Domino & REST: Accessing Domino’s Environment / Check Authentication

2. März 2017 Posted by Sven Hasselbach

If we want to access Domino’s Environment, it is the ContextInfo class which gives us all we need. Everything you need to do to use the class is described in an earlier blog post.

The class gives mainly access to the following methods:

Method Description
getDataDirectory() Path to notes data directory
getEnvironmentString(String envName) Returns the environment variable
getServerDatabase() The actual database as NAPI object, if any
getServerVariable(String varName) Variables from the Request, i.e. „QUERY_STRING“
getUserDatabase() The actual database as Domino Java object, if any
getUserSession() The session of the actual user performing the request (
isAnonymous() true if the current user is Anonymous

Keep in mind that the incoming request is independently of any underlying Notes database. In our example, the URI http://your.server/dominorestservlet/helloworld/ does not run inside of a NSF, that’s why the getServerDatabase() and the getUserDatabase() methods returns null.

As a consequence, our servlet does not have any access restriction and is reachable as anonymous. If you want to access a database programatically, the „normal“ Domino access control is intervening again, but we can do the access check by ourself.

To prevent the access to the servlet, I have added a „checkAuthentication“ method to the RestApiServlet class. This method checks if the current user is a) not anonymous and b) a member of the group RESTAPIAccessAllowed. The method throws a NotAuthenticatedException which let’s the servlet return a HTTP 403.

Domino & REST: Consuming JSON

1. März 2017 Posted by Sven Hasselbach

Consuming JSON is as easy as pie: Just create a new method to the RestApiServlet,  add a @POST annotation, and declare the object you want to the parameters of the method:

    @POST
    @Path("/helloworld/")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response postHelloWorld(HelloWorld helloWorld) {
        return Response.ok(helloWorld, MediaType.APPLICATION_JSON).build();
    }

In this example we are using the same URI „/helloworld/„; because we now are using another HTTP method, there is no conflict between the two methods.

Have a look at the parameter of the postHelloWorld method: It is the HelloWorld class which now instantiated and filled automatically from the POSTed JSON data! We can access the object in Java, and in this short example the result is directly returned back in the response.

If we now send a JSON request with a „message“ field…

curl -i -X POST -d "{\"message\": \"foo\"}" \ 
    -H "Content-Type: application/json" \
    http://your.server/dominorestservlet/helloworld/

… the response contains the message we have posted:

Domino & REST: A basic Servlet

28. Februar 2017 Posted by Sven Hasselbach

To have a good starting point when creating RESTful applications on top of Domino, I have created a „Hello World“ example of a JEE Application, based on Apache Wink & Jackson 2.5.0.

The Jackson AnnotaionProcessor is registered into Apache Wink application and is enabled by default, the JAXB processor is also included as the secondary AnnotationProcessor. The benefit of this is that only one global ObjectMapper instance is created and is reused, wich allows a better performance as when you create an own instance of an ObjectMapper for every request.

The servlet is deployed as a Plugin, so you need a working XPages plugin development environment first before you can build it (I won’t get in the details at this time). The code of the servlet can be found in the domino-rest-servlet.plugin project.

First have a look into plugin.xml: This file contains the extension point used by the plugin, which means that this tells the Domino server what is inside and what to do next with the plugin. The extension point is of type „com.ibm.pvc.webcontainer.application“ and allows to run our own JEE application, independently of the XPages runtime. The contextRoot is the URI part on which our application „listens“. The contentLocation is where files are searched when accessed from the browser, and points to the WebContent folder (as you already know, it is the same as in XPages projects when using the package explorer view). Here you can store static files and/or resources if required.

The web.xml file is the servlet configuration and the place where the servlet is registered. With servlet-mappings you can define URI pathes *inside* of the JEE application.

In this example, every request is mapped to the RestServlet servlet when it is send to „http://your.server/dominorestservlet/„. It will be now processed by the class ch.hasselba.dominorestservlet.RestApiApplication, which „knows“ the RestApiServlet because of the @Path annotations.

Our „Hello World“ example is registered for the URI „/helloworld/“ and only for HTTP GET requests. As soon you are opening „http://your.server/dominorestservlet/helloworld/„, an instance of HelloWorld is created and transformed to JSON. The result looks like this:

{"message":"Hello World!"}

During the next blog posts, I will expand this example and explain step by step how a seamless integration in the Domino environment works.

You can find the code here: https://github.com/hasselbach/domino-rest-servlet

Jackson: Skip Objects conditionally

8. Februar 2017 Posted by Sven Hasselbach

I had a simple problem when implementing this brilliant solution in one of my REST applications: As soon I was using @JsonAnyGetter / @JsonAnySetter and the HidableSerializer together, a NPE was thrown during serialization. The Problem occured in Jackson 1.9.13 and even 2.5.0, the latest version usable with the actual Domino JVM.

  • Stack Trace
org.codehaus.jackson.map.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: ch.hasselba.Test["[anySetter]"])
null
    at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:218)
    at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:183)
    at org.codehaus.jackson.map.ser.std.SerializerBase.wrapAndThrow(SerializerBase.java:140)
    at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:158)
    at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)
at ch.hasselba.HidableSerializer.serialize(HidableSerializer.java:29)
    at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:610)
    at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:256)
    at org.codehaus.jackson.map.ObjectMapper._configAndWriteValue(ObjectMapper.java:2575)
    at org.codehaus.jackson.map.ObjectMapper.writeValueAsString(ObjectMapper.java:2097)
    at ch.hasselba.Demo.main(Demo.java:54)
Caused by: java.lang.NullPointerException
    at org.codehaus.jackson.map.ser.std.MapSerializer.serializeFields(MapSerializer.java:243)
    at org.codehaus.jackson.map.ser.AnyGetterWriter.getAndSerialize(AnyGetterWriter.java:41)
    at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:154)
    ... 7 more

After hours of investigation, a simple solution came up on SO: The default serializer must be resolved, and then the code will work. I just had to override resolve method of my HidableSerializer:

@Override
public void resolve(SerializerProvider serializerProvider) throws JsonMappingException {
    if(defaultSerializer instanceof ResolvableSerializer) {
         ((ResolvableSerializer)defaultSerializer).resolve(serializerProvider);
    }
}

Here is a complete working example:

 

  • The Demo Code
package ch.hasselba;

import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.introspect.BasicBeanDescription;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.map.ser.BeanSerializerModifier;

public class Demo {

    public static void main(String[] args) {

        ObjectMapper mapper = new ObjectMapper();

        // register the module
        Version version = new Version(1, 0, 0, "SNAPSHOT");
        mapper.registerModule(new SimpleModule("HidableModule", version) {
            @Override
            public void setupModule(SetupContext context) {
                super.setupModule(context);
                context.addBeanSerializerModifier(new BeanSerializerModifier() {
                    @SuppressWarnings("unchecked")
                    @Override
                    public JsonSerializer<?> modifySerializer(SerializationConfig config, BasicBeanDescription desc,
                        JsonSerializer<?> serializer) {
                        if (IHidable.class.isAssignableFrom(desc.getBeanClass())) {
                            return new HidableSerializer<Object>((JsonSerializer<Object>) serializer);
                        }
                        return serializer;
                    }
                });
            }
        });

        // the data
        String content = "{ \"foo\": \"bar\" }";

        // build the Object
        Test test = null;
        try {
            test =  mapper.readValue(content, Test.class);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // and now convert it back to a String
        String data = null;
        try {
             data = mapper.writeValueAsString(test);
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println( data );

    }

}

 

  • Test Class
package ch.hasselba;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.codehaus.jackson.annotate.JsonAnyGetter;
import org.codehaus.jackson.annotate.JsonAnySetter;

public class Test implements IHidable {

    private Map<String, Object> others = new ConcurrentHashMap<String, Object>();

    @JsonAnyGetter
    public Map<String, Object> getOthers() {
        return this.others;
    }

    @JsonAnySetter
    public void addOther(final String name, final Object value) {   
        this.others.put(name, value);
    }

    @Override
    public boolean isHidden() {
        return false;
    }

}

 

  • Hideable Serializer
package ch.hasselba;

import java.io.IOException;

import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;

public class HidableSerializer<T> extends JsonSerializer<T> {

    private JsonSerializer<T> defaultSerializer;

    public HidableSerializer(JsonSerializer<T> serializer) {
        defaultSerializer = serializer;
    }

    @Override
    public void serialize(T value, JsonGenerator jgen, SerializerProvider provider)
            throws IOException, JsonProcessingException {

        if( value instanceof IHidable ){
            IHidable hidableValue = (IHidable) value;
            if( hidableValue.isHidden() )
                return;
        }
        defaultSerializer.serialize(value, jgen, provider);
    }

   @Override
   public void resolve(SerializerProvider serializerProvider) throws JsonMappingException {
       if(defaultSerializer instanceof ResolvableSerializer) {
            ((ResolvableSerializer)defaultSerializer).resolve(serializerProvider);
       }
   }
}

 

  • IHidable Interface
package ch.hasselba;

public interface IHidable {
    boolean isHidden();
}