You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by rm...@apache.org on 2021/04/22 08:08:21 UTC

[johnzon] branch master updated: [JOHNZON-340] add JSON-B support in websocket module

This is an automated email from the ASF dual-hosted git repository.

rmannibucau pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/johnzon.git


The following commit(s) were added to refs/heads/master by this push:
     new 73eb0fb  [JOHNZON-340] add JSON-B support in websocket module
73eb0fb is described below

commit 73eb0fbaac15d837432ca6f6531c5e811ef918b1
Author: Romain Manni-Bucau <rm...@gmail.com>
AuthorDate: Thu Apr 22 10:08:15 2021 +0200

    [JOHNZON-340] add JSON-B support in websocket module
---
 johnzon-websocket/pom.xml                          | 14 ++++
 .../TypeAwareDecoder.java}                         | 43 ++---------
 .../lazy/LazySupplier.java}                        | 38 +++++-----
 .../websocket/internal/mapper/MapperLocator.java   | 42 ++---------
 .../internal/mapper/MapperLocatorDelegate.java     | 86 ++++++++++++++++++++++
 .../servlet/IgnoreIfMissing.java}                  | 37 +++++-----
 .../JsonbLocator.java}                             | 31 ++------
 .../websocket/jsonb/JsonbLocatorDelegate.java      | 83 +++++++++++++++++++++
 .../johnzon/websocket/jsonb/JsonbTextDecoder.java  | 70 ++++++++++++++++++
 .../JsonbTextEncoder.java}                         | 24 +++---
 .../websocket/mapper/JohnzonTextDecoder.java       | 10 +--
 .../websocket/mapper/JohnzonTextEncoder.java       |  6 +-
 .../apache/johnzon/websocket/MapperCodecTest.java  |  4 +-
 src/site/markdown/index.md                         | 17 +++++
 14 files changed, 354 insertions(+), 151 deletions(-)

diff --git a/johnzon-websocket/pom.xml b/johnzon-websocket/pom.xml
index c73c2f1..7568633 100644
--- a/johnzon-websocket/pom.xml
+++ b/johnzon-websocket/pom.xml
@@ -49,9 +49,23 @@
     </dependency>
 
     <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jsonb_1.0_spec</artifactId>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
       <groupId>org.apache.johnzon</groupId>
       <artifactId>johnzon-mapper</artifactId>
       <version>${project.version}</version>
+      <scope>provided</scope>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.johnzon</groupId>
+      <artifactId>johnzon-jsonb</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
     </dependency>
 
     <dependency>
diff --git a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextDecoder.java b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/TypeAwareDecoder.java
similarity index 74%
copy from johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextDecoder.java
copy to johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/TypeAwareDecoder.java
index 4530966..0e92215 100644
--- a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextDecoder.java
+++ b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/TypeAwareDecoder.java
@@ -16,51 +16,29 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.johnzon.websocket.mapper;
+package org.apache.johnzon.websocket.internal;
 
-import org.apache.johnzon.mapper.Mapper;
-import org.apache.johnzon.websocket.internal.mapper.MapperLocator;
-
-import java.io.Reader;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Type;
-import javax.websocket.DecodeException;
-import javax.websocket.Decoder;
 import javax.websocket.EndpointConfig;
 import javax.websocket.OnMessage;
 import javax.websocket.Session;
 import javax.websocket.server.PathParam;
 import javax.websocket.server.ServerEndpointConfig;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
 
-public class JohnzonTextDecoder implements Decoder.TextStream<Object> {
-    protected Mapper mapper;
+public abstract class TypeAwareDecoder {
     protected Type type;
 
-    public JohnzonTextDecoder() {
+    public TypeAwareDecoder() {
         // no-op
     }
 
-    // for client side no way to guess the type so let the user provide it easily
-    public JohnzonTextDecoder(final Type type) {
-        this(null, type);
-    }
-
-    public JohnzonTextDecoder(final Mapper mapper, final Type type) {
-        this.mapper = mapper;
+    public TypeAwareDecoder(final Type type) {
         this.type = type;
     }
 
-    @Override
-    public Object decode(final Reader stream) throws DecodeException {
-        return mapper.readObject(stream, type);
-    }
-
-    @Override
-    public void init(final EndpointConfig endpointConfig) {
-        if (mapper == null) {
-            mapper = MapperLocator.locate();
-        }
+    protected void init(final EndpointConfig endpointConfig) {
         if (type != null) {
             return;
         }
@@ -102,9 +80,4 @@ public class JohnzonTextDecoder implements Decoder.TextStream<Object> {
             }
         }
     }
-
-    @Override
-    public void destroy() {
-        // no-op
-    }
 }
diff --git a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/lazy/LazySupplier.java
similarity index 53%
copy from johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java
copy to johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/lazy/LazySupplier.java
index 964c3b5..e8c95e7 100644
--- a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java
+++ b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/lazy/LazySupplier.java
@@ -16,32 +16,32 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.johnzon.websocket.mapper;
+package org.apache.johnzon.websocket.internal.lazy;
 
-import org.apache.johnzon.mapper.Mapper;
-import org.apache.johnzon.websocket.internal.mapper.MapperLocator;
+import java.util.function.Supplier;
 
-import java.io.IOException;
-import java.io.Writer;
-import javax.websocket.EncodeException;
-import javax.websocket.Encoder;
-import javax.websocket.EndpointConfig;
+public class LazySupplier<T> implements Supplier<T> {
+    private Supplier<T> delegate;
+    private volatile T instance;
 
-public class JohnzonTextEncoder implements Encoder.TextStream<Object> {
-    private Mapper mapper;
-
-    @Override
-    public void init(final EndpointConfig endpointConfig) {
-        mapper = MapperLocator.locate();
+    public LazySupplier(final Supplier<T> provider) {
+        this.delegate = provider;
     }
 
     @Override
-    public void destroy() {
-        // no-op
+    public T get() {
+        if (instance == null) {
+            synchronized (this) {
+                if (instance == null) {
+                    instance = delegate.get();
+                    delegate = null;
+                }
+            }
+        }
+        return instance;
     }
 
-    @Override
-    public void encode(final Object object, final Writer writer) throws EncodeException, IOException {
-        mapper.writeObject(object, writer);
+    public T getInstance() {
+        return instance;
     }
 }
diff --git a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/mapper/MapperLocator.java b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/mapper/MapperLocator.java
index 9145694..6596437 100644
--- a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/mapper/MapperLocator.java
+++ b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/mapper/MapperLocator.java
@@ -18,45 +18,17 @@
  */
 package org.apache.johnzon.websocket.internal.mapper;
 
-import org.apache.johnzon.mapper.Mapper;
-import org.apache.johnzon.mapper.MapperBuilder;
+import org.apache.johnzon.websocket.internal.servlet.IgnoreIfMissing;
 
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
 import javax.servlet.annotation.WebListener;
 
-@WebListener
-public class MapperLocator implements ServletContextListener {
-    private static final Map<ClassLoader, Mapper> MAPPER_BY_LOADER = new ConcurrentHashMap<ClassLoader, Mapper>();
-    private static final String ATTRIBUTE = MapperLocator.class.getName() + ".mapper";
-
-    @Override
-    public void contextInitialized(final ServletContextEvent servletContextEvent) {
-        final Mapper build = newMapper();
-        MAPPER_BY_LOADER.put(servletContextEvent.getServletContext().getClassLoader(), build);
-        servletContextEvent.getServletContext().setAttribute(ATTRIBUTE, build);
-    }
-
-    @Override
-    public void contextDestroyed(final ServletContextEvent servletContextEvent) {
-        MAPPER_BY_LOADER.remove(servletContextEvent.getServletContext().getClassLoader());
-    }
-
-    public static Mapper locate() {
-        ClassLoader loader = Thread.currentThread().getContextClassLoader();
-        if (loader == null) {
-            loader = MapperLocator.class.getClassLoader();
-        }
-        final Mapper mapper = MAPPER_BY_LOADER.get(loader);
-        if (mapper == null) {
-            return newMapper();
-        }
-        return mapper;
+@WebListener // since people move to json-b we make this init lazy
+public class MapperLocator extends IgnoreIfMissing {
+    public MapperLocator() {
+        super(() -> new MapperLocatorDelegate());
     }
 
-    private static Mapper newMapper() {
-        return new MapperBuilder().build();
+    public static Object locate() {
+        return MapperLocatorDelegate.locate();
     }
 }
diff --git a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/mapper/MapperLocatorDelegate.java b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/mapper/MapperLocatorDelegate.java
new file mode 100644
index 0000000..48fc30d
--- /dev/null
+++ b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/mapper/MapperLocatorDelegate.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.johnzon.websocket.internal.mapper;
+
+import org.apache.johnzon.mapper.Mapper;
+import org.apache.johnzon.mapper.MapperBuilder;
+import org.apache.johnzon.websocket.internal.lazy.LazySupplier;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+import static java.util.Optional.ofNullable;
+
+public class MapperLocatorDelegate implements ServletContextListener {
+    private static final Map<ClassLoader, Supplier<Mapper>> MAPPER_BY_LOADER = new ConcurrentHashMap<ClassLoader, Supplier<Mapper>>();
+    private static final String ATTRIBUTE = MapperLocator.class.getName() + ".mapper";
+
+    @Override
+    public void contextInitialized(final ServletContextEvent servletContextEvent) {
+        final ServletContext servletContext = servletContextEvent.getServletContext();
+        final Supplier<Mapper> supplier = ofNullable(servletContext.getAttribute(ATTRIBUTE))
+                .map(it -> (Supplier<Mapper>) it)
+                .orElseGet(() -> {
+                    final LazySupplier<Mapper> lazySupplier = new LazySupplier<>(MapperLocatorDelegate::newMapper);
+                    servletContext.setAttribute(ATTRIBUTE, lazySupplier);
+                    return lazySupplier;
+                });
+        MAPPER_BY_LOADER.put(servletContext.getClassLoader(), supplier);
+    }
+
+    @Override
+    public void contextDestroyed(final ServletContextEvent servletContextEvent) {
+        final Supplier<Mapper> supplier = MAPPER_BY_LOADER.remove(servletContextEvent.getServletContext().getClassLoader());
+        if (LazySupplier.class.isInstance(supplier)) {
+            final Object mapper = LazySupplier.class.cast(supplier).getInstance();
+            if (mapper != null) {
+                Mapper.class.cast(mapper).close();
+            }
+        }
+    }
+
+    public static Mapper locate() {
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        if (loader == null) {
+            loader = MapperLocatorDelegate.class.getClassLoader();
+        }
+        Supplier<Mapper> mapper = MAPPER_BY_LOADER.get(loader);
+        if (mapper == null) {
+            synchronized (MAPPER_BY_LOADER) {
+                mapper = MAPPER_BY_LOADER.get(loader);
+                if (mapper != null) {
+                    return mapper.get();
+                }
+                final Mapper instance = newMapper();
+                mapper = () -> instance;
+                MAPPER_BY_LOADER.put(loader, mapper);
+                return mapper.get();
+            }
+        }
+        return mapper.get();
+    }
+
+    private static Mapper newMapper() {
+        return new MapperBuilder().build();
+    }
+}
diff --git a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/servlet/IgnoreIfMissing.java
similarity index 51%
copy from johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java
copy to johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/servlet/IgnoreIfMissing.java
index 964c3b5..f91344e 100644
--- a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java
+++ b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/internal/servlet/IgnoreIfMissing.java
@@ -16,32 +16,33 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.johnzon.websocket.mapper;
+package org.apache.johnzon.websocket.internal.servlet;
 
-import org.apache.johnzon.mapper.Mapper;
-import org.apache.johnzon.websocket.internal.mapper.MapperLocator;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import java.util.function.Supplier;
 
-import java.io.IOException;
-import java.io.Writer;
-import javax.websocket.EncodeException;
-import javax.websocket.Encoder;
-import javax.websocket.EndpointConfig;
+public class IgnoreIfMissing implements ServletContextListener {
+    private final Supplier<ServletContextListener> delegate;
+    private boolean skipped;
 
-public class JohnzonTextEncoder implements Encoder.TextStream<Object> {
-    private Mapper mapper;
-
-    @Override
-    public void init(final EndpointConfig endpointConfig) {
-        mapper = MapperLocator.locate();
+    public IgnoreIfMissing(final Supplier<ServletContextListener> delegate) {
+        this.delegate = delegate;
     }
 
     @Override
-    public void destroy() {
-        // no-op
+    public void contextInitialized(final ServletContextEvent sce) {
+        try {
+            delegate.get().contextInitialized(sce);
+        } catch (final Error | RuntimeException re) {
+            skipped = true;
+        }
     }
 
     @Override
-    public void encode(final Object object, final Writer writer) throws EncodeException, IOException {
-        mapper.writeObject(object, writer);
+    public void contextDestroyed(final ServletContextEvent sce) {
+        if (!skipped) {
+            delegate.get().contextDestroyed(sce);
+        }
     }
 }
diff --git a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/jsonb/JsonbLocator.java
similarity index 51%
copy from johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java
copy to johnzon-websocket/src/main/java/org/apache/johnzon/websocket/jsonb/JsonbLocator.java
index 964c3b5..d84490d 100644
--- a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java
+++ b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/jsonb/JsonbLocator.java
@@ -16,32 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.johnzon.websocket.mapper;
+package org.apache.johnzon.websocket.jsonb;
 
-import org.apache.johnzon.mapper.Mapper;
-import org.apache.johnzon.websocket.internal.mapper.MapperLocator;
+import org.apache.johnzon.websocket.internal.servlet.IgnoreIfMissing;
 
-import java.io.IOException;
-import java.io.Writer;
-import javax.websocket.EncodeException;
-import javax.websocket.Encoder;
-import javax.websocket.EndpointConfig;
+import javax.servlet.annotation.WebListener;
 
-public class JohnzonTextEncoder implements Encoder.TextStream<Object> {
-    private Mapper mapper;
-
-    @Override
-    public void init(final EndpointConfig endpointConfig) {
-        mapper = MapperLocator.locate();
-    }
-
-    @Override
-    public void destroy() {
-        // no-op
-    }
-
-    @Override
-    public void encode(final Object object, final Writer writer) throws EncodeException, IOException {
-        mapper.writeObject(object, writer);
+@WebListener
+public class JsonbLocator extends IgnoreIfMissing {
+    public JsonbLocator() {
+        super(() -> new JsonbLocatorDelegate());
     }
 }
diff --git a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/jsonb/JsonbLocatorDelegate.java b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/jsonb/JsonbLocatorDelegate.java
new file mode 100644
index 0000000..3e1032a
--- /dev/null
+++ b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/jsonb/JsonbLocatorDelegate.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.johnzon.websocket.jsonb;
+
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static java.util.Optional.ofNullable;
+
+public class JsonbLocatorDelegate implements ServletContextListener {
+    private static final Map<ClassLoader, Jsonb> BY_LOADER = new ConcurrentHashMap<>();
+    private static final String ATTRIBUTE = JsonbLocator.class.getName() + ".jsonb";
+
+    @Override
+    public void contextInitialized(final ServletContextEvent servletContextEvent) {
+        final ServletContext servletContext = servletContextEvent.getServletContext();
+        final Jsonb instance = ofNullable(servletContext.getAttribute(ATTRIBUTE))
+                .map(Jsonb.class::cast)
+                .orElseGet(() -> {
+                    final Jsonb jsonb = newInstance();
+                    servletContext.setAttribute(ATTRIBUTE, jsonb);
+                    return jsonb;
+                });
+        BY_LOADER.put(servletContext.getClassLoader(), instance);
+    }
+
+    @Override
+    public void contextDestroyed(final ServletContextEvent servletContextEvent) {
+        final Jsonb instance = BY_LOADER.remove(servletContextEvent.getServletContext().getClassLoader());
+        if (instance != null) {
+            try {
+                instance.close();
+            } catch (final Exception e) {
+                throw new IllegalStateException(e.getMessage(), e);
+            }
+        }
+    }
+
+    public static Jsonb locate() {
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        if (loader == null) {
+            loader = JsonbLocatorDelegate.class.getClassLoader();
+        }
+        Jsonb jsonb = BY_LOADER.get(loader);
+        if (jsonb == null) {
+            synchronized (BY_LOADER) {
+                jsonb = BY_LOADER.get(loader);
+                if (jsonb != null) {
+                    return jsonb;
+                }
+                jsonb = newInstance();
+                BY_LOADER.put(loader, jsonb);
+                return jsonb;
+            }
+        }
+        return jsonb;
+    }
+
+    private static Jsonb newInstance() {
+        return JsonbBuilder.create();
+    }
+}
diff --git a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/jsonb/JsonbTextDecoder.java b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/jsonb/JsonbTextDecoder.java
new file mode 100644
index 0000000..c010e89
--- /dev/null
+++ b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/jsonb/JsonbTextDecoder.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.johnzon.websocket.jsonb;
+
+import org.apache.johnzon.websocket.internal.TypeAwareDecoder;
+
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbException;
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+import java.io.Reader;
+import java.lang.reflect.Type;
+
+public class JsonbTextDecoder extends TypeAwareDecoder implements Decoder.TextStream<Object> {
+    protected Jsonb mapper;
+    protected Type type;
+
+    public JsonbTextDecoder() {
+        // no-op
+    }
+
+    // for client side no way to guess the type so let the user provide it easily
+    public JsonbTextDecoder(final Type type) {
+        this(null, type);
+    }
+
+    public JsonbTextDecoder(final Jsonb jsonb, final Type type) {
+        super(type);
+        this.mapper = jsonb;
+    }
+
+    @Override
+    public Object decode(final Reader stream) throws DecodeException {
+        try {
+            return mapper.fromJson(stream, type);
+        } catch (final JsonbException je) {
+            throw new DecodeException("", je.getMessage(), je);
+        }
+    }
+
+    @Override
+    public void init(final EndpointConfig endpointConfig) {
+        if (mapper == null) {
+            mapper = JsonbLocatorDelegate.locate();
+        }
+        super.init(endpointConfig);
+    }
+
+    @Override
+    public void destroy() {
+        // no-op
+    }
+}
diff --git a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/jsonb/JsonbTextEncoder.java
similarity index 71%
copy from johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java
copy to johnzon-websocket/src/main/java/org/apache/johnzon/websocket/jsonb/JsonbTextEncoder.java
index 964c3b5..bf1873c 100644
--- a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java
+++ b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/jsonb/JsonbTextEncoder.java
@@ -16,23 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.johnzon.websocket.mapper;
+package org.apache.johnzon.websocket.jsonb;
 
-import org.apache.johnzon.mapper.Mapper;
-import org.apache.johnzon.websocket.internal.mapper.MapperLocator;
-
-import java.io.IOException;
-import java.io.Writer;
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbException;
 import javax.websocket.EncodeException;
 import javax.websocket.Encoder;
 import javax.websocket.EndpointConfig;
+import java.io.Writer;
 
-public class JohnzonTextEncoder implements Encoder.TextStream<Object> {
-    private Mapper mapper;
+public class JsonbTextEncoder implements Encoder.TextStream<Object> {
+    private Jsonb jsonb;
 
     @Override
     public void init(final EndpointConfig endpointConfig) {
-        mapper = MapperLocator.locate();
+        jsonb = JsonbLocatorDelegate.locate();
     }
 
     @Override
@@ -41,7 +39,11 @@ public class JohnzonTextEncoder implements Encoder.TextStream<Object> {
     }
 
     @Override
-    public void encode(final Object object, final Writer writer) throws EncodeException, IOException {
-        mapper.writeObject(object, writer);
+    public void encode(final Object object, final Writer writer) throws EncodeException {
+        try {
+            jsonb.toJson(object, writer);
+        } catch (final JsonbException je) {
+            throw new EncodeException(object, je.getMessage(), je);
+        }
     }
 }
diff --git a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextDecoder.java b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextDecoder.java
index 4530966..60c27fa 100644
--- a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextDecoder.java
+++ b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextDecoder.java
@@ -21,10 +21,6 @@ package org.apache.johnzon.websocket.mapper;
 import org.apache.johnzon.mapper.Mapper;
 import org.apache.johnzon.websocket.internal.mapper.MapperLocator;
 
-import java.io.Reader;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Type;
 import javax.websocket.DecodeException;
 import javax.websocket.Decoder;
 import javax.websocket.EndpointConfig;
@@ -32,6 +28,10 @@ import javax.websocket.OnMessage;
 import javax.websocket.Session;
 import javax.websocket.server.PathParam;
 import javax.websocket.server.ServerEndpointConfig;
+import java.io.Reader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
 
 public class JohnzonTextDecoder implements Decoder.TextStream<Object> {
     protected Mapper mapper;
@@ -59,7 +59,7 @@ public class JohnzonTextDecoder implements Decoder.TextStream<Object> {
     @Override
     public void init(final EndpointConfig endpointConfig) {
         if (mapper == null) {
-            mapper = MapperLocator.locate();
+            mapper = Mapper.class.cast(MapperLocator.locate());
         }
         if (type != null) {
             return;
diff --git a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java
index 964c3b5..015daa9 100644
--- a/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java
+++ b/johnzon-websocket/src/main/java/org/apache/johnzon/websocket/mapper/JohnzonTextEncoder.java
@@ -21,18 +21,18 @@ package org.apache.johnzon.websocket.mapper;
 import org.apache.johnzon.mapper.Mapper;
 import org.apache.johnzon.websocket.internal.mapper.MapperLocator;
 
-import java.io.IOException;
-import java.io.Writer;
 import javax.websocket.EncodeException;
 import javax.websocket.Encoder;
 import javax.websocket.EndpointConfig;
+import java.io.IOException;
+import java.io.Writer;
 
 public class JohnzonTextEncoder implements Encoder.TextStream<Object> {
     private Mapper mapper;
 
     @Override
     public void init(final EndpointConfig endpointConfig) {
-        mapper = MapperLocator.locate();
+        mapper = Mapper.class.cast(MapperLocator.locate());
     }
 
     @Override
diff --git a/johnzon-websocket/src/test/java/org/apache/johnzon/websocket/MapperCodecTest.java b/johnzon-websocket/src/test/java/org/apache/johnzon/websocket/MapperCodecTest.java
index 9b2770c..2dcc209 100644
--- a/johnzon-websocket/src/test/java/org/apache/johnzon/websocket/MapperCodecTest.java
+++ b/johnzon-websocket/src/test/java/org/apache/johnzon/websocket/MapperCodecTest.java
@@ -26,6 +26,8 @@ import org.apache.johnzon.websocket.endpoint.Message;
 import org.apache.johnzon.websocket.endpoint.ServerEndpointImpl;
 import org.apache.johnzon.websocket.endpoint.ServerReport;
 import org.apache.johnzon.websocket.internal.mapper.MapperLocator;
+import org.apache.johnzon.websocket.internal.mapper.MapperLocatorDelegate;
+import org.apache.johnzon.websocket.internal.servlet.IgnoreIfMissing;
 import org.apache.johnzon.websocket.mapper.JohnzonTextDecoder;
 import org.apache.johnzon.websocket.mapper.JohnzonTextEncoder;
 import org.apache.openejb.arquillian.common.IO;
@@ -58,7 +60,7 @@ public class MapperCodecTest {
                 .addClasses(ServerEndpointImpl.class, ServerReport.class, Message.class)
                 .addAsLibrary(
                         ShrinkWrap.create(JavaArchive.class, "johnzon-websocket.jar")
-                            .addClasses(MapperLocator.class, JohnzonTextDecoder.class, JohnzonTextEncoder.class)
+                            .addClasses(MapperLocator.class, MapperLocatorDelegate.class, IgnoreIfMissing.class, JohnzonTextDecoder.class, JohnzonTextEncoder.class)
                             .addPackages(true, JsonProviderImpl.class.getPackage())
                             .addPackages(true, Mapper.class.getPackage()))
                 .addAsLibrary(jarLocation(Json.class));
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index 867cec7..f97517b 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -391,6 +391,13 @@ Integration is at codec level (encoder/decoder). There are two families of codec
 * The ones based on JSON-P (JsonObject, JsonArray, JsonStructure)
 * The ones based on Johnzon Mapper
 
+Note that if you want to control the Mapper or JSON-B instance used by decoders you can set up the associated servlet listeners:
+
+* org.apache.johnzon.websocket.internal.mapper.MapperLocator for johnzon-mapper
+* org.apache.johnzon.websocket.jsonb.JsonbLocator for JSON-B
+
+if you write in the servlet context an attribute named `org.apache.johnzon.websocket.internal.mapper.MapperLocator.mapper` (it is a `Supplier<Mapper>`) or `org.apache.johnzon.websocket.jsonb.JsonbLocator.jsonb` (depending the implementation you use) it will be used instead of the default instance.
+
 #### JSON-P integration
 
 Encoders:
@@ -415,6 +422,16 @@ Decoder:
 
 *  `org.apache.johnzon.websocket.mapper.JohnzonTextDecoder`
 
+#### JSON-B integration
+
+Encoder:
+
+*  `org.apache.johnzon.websocket.jsonb.JsonbTextEncoder`
+
+Decoder:
+
+*  `org.apache.johnzon.websocket.jsonb.JsonbTextDecoder`
+
 #### Sample
 
 ##### JSON-P Samples