Java tutorial
/* * Copyright (c) 2008-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.cometd.client; import java.io.StringReader; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.cometd.bayeux.Message; import org.cometd.bayeux.client.ClientSession; import org.cometd.bayeux.client.ClientSessionChannel; import org.cometd.bayeux.server.LocalSession; import org.cometd.client.transport.ClientTransport; import org.cometd.client.transport.LongPollingTransport; import org.cometd.common.JSONContext; import org.cometd.common.Jackson1JSONContextClient; import org.cometd.common.Jackson2JSONContextClient; import org.cometd.server.AbstractServerTransport; import org.cometd.server.Jackson1JSONContextServer; import org.cometd.server.Jackson2JSONContextServer; import org.cometd.server.transport.AbstractHttpTransport; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class JacksonCustomSerializationTest extends ClientServerTest { @Parameters(name = "{index}: Jackson Context Server: {0} Jackson Context Client: {1}") public static Iterable<Object[]> data() { return Arrays.asList( new Object[][] { { TestJackson2JSONContextServer.class, TestJackson2JSONContextClient.class }, { TestJackson1JSONContextServer.class, TestJackson1JSONContextClient.class }, }); } private final String jacksonContextServerClassName; private final String jacksonContextClientClassName; public JacksonCustomSerializationTest(final Class<?> jacksonContextServerClass, final Class<?> jacksonContextClientClass) { this.jacksonContextServerClassName = jacksonContextServerClass.getName(); this.jacksonContextClientClassName = jacksonContextClientClass.getName(); } @Test public void testJacksonCustomSerialization() throws Exception { Map<String, String> serverOptions = new HashMap<>(); serverOptions.put(AbstractServerTransport.JSON_CONTEXT_OPTION, jacksonContextServerClassName); serverOptions.put(AbstractHttpTransport.JSON_DEBUG_OPTION, "true"); Map<String, Object> clientOptions = new HashMap<>(); clientOptions.put(ClientTransport.JSON_CONTEXT_OPTION, jacksonContextClientClassName); startServer(serverOptions); String channelName = "/data"; final String dataContent = "random"; final long extraContent = 13; final CountDownLatch latch = new CountDownLatch(1); LocalSession service = bayeux.newLocalSession("custom_serialization"); service.handshake(); service.getChannel(channelName).subscribe(new ClientSessionChannel.MessageListener() { public void onMessage(ClientSessionChannel channel, Message message) { Data data = (Data) message.getData(); Assert.assertEquals(dataContent, data.content); Map<String, Object> ext = message.getExt(); Assert.assertNotNull(ext); Extra extra = (Extra) ext.get("extra"); Assert.assertEquals(extraContent, extra.content); latch.countDown(); } }); BayeuxClient client = new BayeuxClient(cometdURL, new LongPollingTransport(clientOptions, httpClient)); client.addExtension(new ExtraExtension(extraContent)); client.handshake(); Assert.assertTrue(client.waitFor(5000, BayeuxClient.State.CONNECTED)); // Wait for the connect to establish Thread.sleep(1000); client.getChannel(channelName).publish(new Data(dataContent)); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); disconnectBayeuxClient(client); } @Test public void testParserGenerator() throws Exception { // Note: Jackson does not seem to be able to serialize/deserialize correctly a single Data/Extra object. // However, if they are put into a container like a Map, then Jackson produces a different JSON than // what it produces for the standalone object that allows correct deserialization, of this form: // { field: ["className", {object}] } // It is way easier to have Jetty serialize and deserialize this form than make Jackson use Jetty's form. // They problem is that Jackson tries to be "smart" in figuring out the typing, but with a Map<String, Object> // there is no way to have type information for the values, so Jackson defaults to a basic deserializer // that either is not very flexible, or it's very difficult to configure, so much that I could not so far. JSONContext.Client jsonContext = (JSONContext.Client) getClass().getClassLoader() .loadClass(jacksonContextClientClassName).newInstance(); Data data1 = new Data("data"); Extra extra1 = new Extra(42L); Map<String, Object> map1 = new HashMap<>(); map1.put("data", data1); map1.put("extra", extra1); String json = jsonContext.getGenerator().generate(map1); Map map2 = jsonContext.getParser().parse(new StringReader(json), Map.class); Data data2 = (Data) map2.get("data"); Extra extra2 = (Extra) map2.get("extra"); Assert.assertEquals(data1.content, data2.content); Assert.assertEquals(extra1.content, extra2.content); } private static class ExtraExtension extends ClientSession.Extension.Adapter { private final long content; public ExtraExtension(long content) { this.content = content; } @Override public boolean send(ClientSession session, Message.Mutable message) { Map<String, Object> ext = message.getExt(true); ext.put("extra", new Extra(content)); return true; } } public static class TestJackson1JSONContextServer extends Jackson1JSONContextServer { public TestJackson1JSONContextServer() { getObjectMapper() .enableDefaultTyping(org.codehaus.jackson.map.ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT); } } public static class TestJackson1JSONContextClient extends Jackson1JSONContextClient { public TestJackson1JSONContextClient() { getObjectMapper() .enableDefaultTyping(org.codehaus.jackson.map.ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT); } } public static class TestJackson2JSONContextServer extends Jackson2JSONContextServer { public TestJackson2JSONContextServer() { getObjectMapper().enableDefaultTyping( com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT); } } public static class TestJackson2JSONContextClient extends Jackson2JSONContextClient { public TestJackson2JSONContextClient() { getObjectMapper().enableDefaultTyping( com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT); } } private static class Data { @com.fasterxml.jackson.annotation.JsonProperty @org.codehaus.jackson.annotate.JsonProperty private String content; private Data() { // Needed by Jackson } private Data(String content) { this.content = content; } } private static class Extra { @com.fasterxml.jackson.annotation.JsonProperty @org.codehaus.jackson.annotate.JsonProperty private long content; private Extra() { // Needed by Jackson } private Extra(long content) { this.content = content; } } }