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 2016/01/29 14:35:31 UTC

incubator-johnzon git commit: JOHNZON-64 CDI support for @JsonbAdapter

Repository: incubator-johnzon
Updated Branches:
  refs/heads/master 6673b73c6 -> d271dec4e


JOHNZON-64 CDI support for @JsonbAdapter


Project: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/commit/d271dec4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/tree/d271dec4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/diff/d271dec4

Branch: refs/heads/master
Commit: d271dec4ea6ee7bb75805105291d217f1d2129dd
Parents: 6673b73
Author: Romain Manni-Bucau <rm...@gmail.org>
Authored: Fri Jan 29 14:35:22 2016 +0100
Committer: Romain Manni-Bucau <rm...@gmail.org>
Committed: Fri Jan 29 14:35:22 2016 +0100

----------------------------------------------------------------------
 johnzon-jsonb/pom.xml                           |  24 +++++
 .../org/apache/johnzon/jsonb/JohnsonJsonb.java  |   7 +-
 .../apache/johnzon/jsonb/JohnzonBuilder.java    |  50 +++++++--
 .../apache/johnzon/jsonb/JsonbAccessMode.java   |  25 ++++-
 .../jsonb/factory/CdiJohnzonAdapterFactory.java |  71 +++++++++++++
 .../factory/SimpleJohnzonAdapterFactory.java    |  32 ++++++
 .../jsonb/spi/JohnzonAdapterFactory.java        |  48 +++++++++
 .../apache/johnzon/jsonb/CdiAdapterTest.java    | 103 +++++++++++++++++++
 .../java/org/apache/johnzon/mapper/Mapper.java  |  27 ++++-
 .../apache/johnzon/mapper/MapperBuilder.java    |  12 ++-
 10 files changed, 386 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/d271dec4/johnzon-jsonb/pom.xml
----------------------------------------------------------------------
diff --git a/johnzon-jsonb/pom.xml b/johnzon-jsonb/pom.xml
index a9a0c26..df97066 100644
--- a/johnzon-jsonb/pom.xml
+++ b/johnzon-jsonb/pom.xml
@@ -43,6 +43,12 @@
       <version>2.0</version>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jcdi_1.1_spec</artifactId>
+      <version>1.0</version>
+      <scope>provided</scope>
+    </dependency>
 
     <dependency>
       <groupId>org.apache.johnzon</groupId>
@@ -74,5 +80,23 @@
       <version>${cxf.version}</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-interceptor_1.2_spec</artifactId>
+      <version>1.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-atinject_1.0_spec</artifactId>
+      <version>1.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.openwebbeans</groupId>
+      <artifactId>openwebbeans-impl</artifactId>
+      <version>1.6.2</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/d271dec4/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnsonJsonb.java
----------------------------------------------------------------------
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnsonJsonb.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnsonJsonb.java
index d77c423..7308374 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnsonJsonb.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnsonJsonb.java
@@ -39,7 +39,7 @@ import java.util.OptionalInt;
 import java.util.OptionalLong;
 
 // TODO: Optional handling for lists (and arrays)?
-public class JohnsonJsonb implements Jsonb {
+public class JohnsonJsonb implements Jsonb, AutoCloseable {
     private final Mapper delegate;
 
     public JohnsonJsonb(final Mapper build) {
@@ -360,4 +360,9 @@ public class JohnsonJsonb implements Jsonb {
         final Type rawType = ParameterizedType.class.cast(runtimeType).getRawType();
         return Class.class.isInstance(rawType) && Collection.class.isAssignableFrom(Class.class.cast(rawType));
     }
+
+    @Override
+    public void close() {
+        delegate.close();
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/d271dec4/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
----------------------------------------------------------------------
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
index 2ccee9f..a43f2cd 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
@@ -22,6 +22,8 @@ import org.apache.johnzon.core.AbstractJsonFactory;
 import org.apache.johnzon.core.JsonGeneratorFactoryImpl;
 import org.apache.johnzon.jsonb.converter.JsonbConverterFromString;
 import org.apache.johnzon.jsonb.converter.JsonbConverterToString;
+import org.apache.johnzon.jsonb.factory.SimpleJohnzonAdapterFactory;
+import org.apache.johnzon.jsonb.spi.JohnzonAdapterFactory;
 import org.apache.johnzon.mapper.Converter;
 import org.apache.johnzon.mapper.MapperBuilder;
 
@@ -66,6 +68,7 @@ import java.util.concurrent.ConcurrentMap;
 import java.util.stream.Stream;
 
 import static java.util.Collections.emptyMap;
+import static java.util.Optional.ofNullable;
 import static javax.json.bind.config.PropertyNamingStrategy.IDENTITY;
 import static javax.json.bind.config.PropertyOrderStrategy.LEXICOGRAPHICAL;
 
@@ -151,11 +154,31 @@ public class JohnzonBuilder implements JsonbBuilder {
         final Map<Class<?>, Converter<?>> defaultConverters = createJava8Converters();
         defaultConverters.forEach(builder::addConverter);
 
-        builder.setAccessMode(
-            new JsonbAccessMode(
-                propertyNamingStrategy, orderValue, visibilityStrategy,
-                !namingStrategyValue.orElse("").equals(PropertyNamingStrategy.CASE_INSENSITIVE),
-                defaultConverters));
+        final JsonbAccessMode accessMode = new JsonbAccessMode(
+            propertyNamingStrategy, orderValue, visibilityStrategy,
+            !namingStrategyValue.orElse("").equals(PropertyNamingStrategy.CASE_INSENSITIVE),
+            defaultConverters,
+            config.getProperty("johnzon.factory").map(val -> {
+                if (JohnzonAdapterFactory.class.isInstance(val)) {
+                    return JohnzonAdapterFactory.class.cast(val);
+                }
+                if (String.class.isInstance(val)) {
+                    try {
+                        return JohnzonAdapterFactory.class.cast(tccl().loadClass(val.toString()).newInstance());
+                    } catch (final InstantiationException | ClassNotFoundException | IllegalAccessException e) {
+                        throw new IllegalArgumentException(e);
+                    }
+                }
+                if (Class.class.isInstance(val)) {
+                    try {
+                        return JohnzonAdapterFactory.class.cast(Class.class.cast(val).newInstance());
+                    } catch (final InstantiationException | IllegalAccessException e) {
+                        throw new IllegalArgumentException(e);
+                    }
+                }
+                throw new IllegalArgumentException("Unsupported factory: " + val);
+            }).orElseGet(this::findFactory));
+        builder.setAccessMode(accessMode);
 
 
         // user adapters
@@ -201,7 +224,22 @@ public class JohnzonBuilder implements JsonbBuilder {
             }
         });
 
-        return new JohnsonJsonb(builder.build());
+        return new JohnsonJsonb(builder.addCloseable(accessMode).build());
+    }
+
+    private JohnzonAdapterFactory findFactory() {
+        try { // don't trigger CDI is not there
+            final Class<?> cdi = tccl().loadClass("javax.enterprise.inject.spi.CDI");
+            final Object cdiInstance = cdi.getMethod("current").invoke(null);
+            final Object beanManager = cdi.getMethod("getBeanManager").invoke(cdiInstance);
+            return new org.apache.johnzon.jsonb.factory.CdiJohnzonAdapterFactory(beanManager);
+        } catch (final NoClassDefFoundError | Exception e) {
+            return new SimpleJohnzonAdapterFactory();
+        }
+    }
+
+    private ClassLoader tccl() {
+        return ofNullable(Thread.currentThread().getContextClassLoader()).orElseGet(ClassLoader::getSystemClassLoader);
     }
 
     private static Map<Class<?>, Converter<?>> createJava8Converters() { // TODO: move these converters in converter package

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/d271dec4/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
----------------------------------------------------------------------
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
index f155e35..00ae9b0 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
@@ -26,6 +26,7 @@ import org.apache.johnzon.jsonb.converter.JsonbLocalDateTimeConverter;
 import org.apache.johnzon.jsonb.converter.JsonbNumberConverter;
 import org.apache.johnzon.jsonb.converter.JsonbValueConverter;
 import org.apache.johnzon.jsonb.converter.JsonbZonedDateTimeConverter;
+import org.apache.johnzon.jsonb.spi.JohnzonAdapterFactory;
 import org.apache.johnzon.mapper.Converter;
 import org.apache.johnzon.mapper.access.AccessMode;
 import org.apache.johnzon.mapper.access.FieldAccessMode;
@@ -46,6 +47,8 @@ import javax.json.bind.annotation.JsonbValue;
 import javax.json.bind.config.PropertyNamingStrategy;
 import javax.json.bind.config.PropertyOrderStrategy;
 import javax.json.bind.config.PropertyVisibilityStrategy;
+import java.io.Closeable;
+import java.io.IOException;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -58,6 +61,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
@@ -76,23 +80,26 @@ import static java.util.Arrays.asList;
 import static java.util.Optional.ofNullable;
 import static org.apache.johnzon.mapper.reflection.Converters.matches;
 
-public class JsonbAccessMode implements AccessMode {
+public class JsonbAccessMode implements AccessMode, Closeable {
     private final PropertyNamingStrategy naming;
     private final String order;
     private final PropertyVisibilityStrategy visibility;
     private final FieldAndMethodAccessMode delegate;
     private final boolean caseSensitive;
     private final Map<Class<?>, Converter<?>> defaultConverters;
+    private final JohnzonAdapterFactory factory;
+    private final Collection<JohnzonAdapterFactory.Instance<?>> toRelease = new ArrayList<>();
 
     public JsonbAccessMode(final PropertyNamingStrategy propertyNamingStrategy, final String orderValue,
                            final PropertyVisibilityStrategy visibilityStrategy, final boolean caseSensitive,
-                           final Map<Class<?>, Converter<?>> defaultConverters) {
+                           final Map<Class<?>, Converter<?>> defaultConverters, final JohnzonAdapterFactory factory) {
         this.naming = propertyNamingStrategy;
         this.order = orderValue;
         this.visibility = visibilityStrategy;
         this.caseSensitive = caseSensitive;
         this.delegate = new FieldAndMethodAccessMode(true, true);
         this.defaultConverters = defaultConverters;
+        this.factory = factory;
     }
 
     @Override
@@ -269,7 +276,9 @@ public class JsonbAccessMode implements AccessMode {
             }
             final Type[] args = pt.getActualTypeArguments();
             final boolean fromString = args[0] == String.class;
-            converter = fromString ? new JsonbConverterFromString<>(value.newInstance()) : new JsonbConverterToString<>(value.newInstance());
+            final JohnzonAdapterFactory.Instance<? extends JsonbAdapter> instance = newAdapter(value);
+            toRelease.add(instance);
+            converter = fromString ? new JsonbConverterFromString(instance.getValue()) : new JsonbConverterToString(instance.getValue());
         } else if (dateFormat != null) { // TODO: support lists, LocalDate?
             if (Date.class == type) {
                 converter = new JsonbDateConverter(dateFormat);
@@ -290,6 +299,10 @@ public class JsonbAccessMode implements AccessMode {
         return converter;
     }
 
+    private JohnzonAdapterFactory.Instance<? extends JsonbAdapter> newAdapter(final Class<? extends JsonbAdapter> value) {
+        return factory.create(value);
+    }
+
     @Override
     public Map<String, Reader> findReaders(final Class<?> clazz) {
         final Map<String, Reader> readers = delegate.findReaders(clazz);
@@ -544,4 +557,10 @@ public class JsonbAccessMode implements AccessMode {
         }
         return keyComparator;
     }
+
+    @Override
+    public void close() throws IOException {
+        toRelease.forEach(JohnzonAdapterFactory.Instance::release);
+        toRelease.clear();
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/d271dec4/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/factory/CdiJohnzonAdapterFactory.java
----------------------------------------------------------------------
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/factory/CdiJohnzonAdapterFactory.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/factory/CdiJohnzonAdapterFactory.java
new file mode 100644
index 0000000..aa2c2b6
--- /dev/null
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/factory/CdiJohnzonAdapterFactory.java
@@ -0,0 +1,71 @@
+/*
+ * 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.jsonb.factory;
+
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import java.util.Set;
+
+public class CdiJohnzonAdapterFactory extends SimpleJohnzonAdapterFactory {
+    private final BeanManager bm;
+
+    public CdiJohnzonAdapterFactory(final Object bm) {
+        this.bm = BeanManager.class.cast(bm);
+    }
+
+    @Override
+    public <T> Instance<T> create(final Class<T> type) {
+        try {
+            final Set<Bean<?>> beans = bm.getBeans(type);
+            final Bean<?> bean = bm.resolve(beans);
+            if (bean != null) {
+                final CreationalContext<Object> creationalContext = bm.createCreationalContext(null);
+                final T instance = (T) bm.getReference(bean, type, creationalContext);
+                if (bm.isNormalScope(bean.getScope())) {
+                    return new ConstantInstance<>((T) bm.getReference(bean, type, creationalContext));
+                }
+                return new CdiInstance<T>(instance, creationalContext);
+            }
+        } catch (final Exception e) {
+            // fallback
+        }
+        return super.create(type);
+    }
+
+    private static class CdiInstance<T> implements Instance<T> {
+        private final T value;
+        private final CreationalContext<Object> context;
+
+        private CdiInstance(final T instance, final CreationalContext<Object> creationalContext) {
+            this.value = instance;
+            this.context = creationalContext;
+        }
+
+        @Override
+        public T getValue() {
+            return value;
+        }
+
+        @Override
+        public void release() {
+            context.release();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/d271dec4/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/factory/SimpleJohnzonAdapterFactory.java
----------------------------------------------------------------------
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/factory/SimpleJohnzonAdapterFactory.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/factory/SimpleJohnzonAdapterFactory.java
new file mode 100644
index 0000000..0f19476
--- /dev/null
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/factory/SimpleJohnzonAdapterFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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.jsonb.factory;
+
+import org.apache.johnzon.jsonb.spi.JohnzonAdapterFactory;
+
+public class SimpleJohnzonAdapterFactory implements JohnzonAdapterFactory {
+    @Override
+    public <T> Instance<T> create(Class<T> type) {
+        try {
+            return new ConstantInstance<>(type.newInstance());
+        } catch (final InstantiationException | IllegalAccessException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/d271dec4/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/spi/JohnzonAdapterFactory.java
----------------------------------------------------------------------
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/spi/JohnzonAdapterFactory.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/spi/JohnzonAdapterFactory.java
new file mode 100644
index 0000000..57ca6e6
--- /dev/null
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/spi/JohnzonAdapterFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.jsonb.spi;
+
+import java.io.Serializable;
+
+public interface JohnzonAdapterFactory {
+    <T> Instance<T> create(final Class<T> type);
+
+    interface Instance<T> extends Serializable {
+        T getValue();
+        void release();
+    }
+
+    class ConstantInstance<T> implements Instance<T> {
+        private final T value;
+
+        public ConstantInstance(final T value) {
+            this.value = value;
+        }
+
+        @Override
+        public T getValue() {
+            return value;
+        }
+
+        @Override
+        public void release() {
+            // no-op
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/d271dec4/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/CdiAdapterTest.java
----------------------------------------------------------------------
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/CdiAdapterTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/CdiAdapterTest.java
new file mode 100644
index 0000000..e966052
--- /dev/null
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/CdiAdapterTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.jsonb;
+
+import org.apache.webbeans.config.WebBeansContext;
+import org.apache.webbeans.config.WebBeansFinder;
+import org.apache.webbeans.lifecycle.test.OpenWebBeansTestLifeCycle;
+import org.apache.webbeans.lifecycle.test.OpenWebBeansTestMetaDataDiscoveryService;
+import org.apache.webbeans.proxy.OwbNormalScopeProxy;
+import org.apache.webbeans.util.WebBeansUtil;
+import org.junit.Test;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.json.bind.adapter.JsonbAdapter;
+import javax.json.bind.annotation.JsonbTypeAdapter;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class CdiAdapterTest {
+    @Test
+    public void run() {
+        WebBeansFinder.clearInstances(WebBeansUtil.getCurrentClassLoader());
+        final OpenWebBeansTestLifeCycle testLifecycle = new OpenWebBeansTestLifeCycle();
+        final WebBeansContext ctx = WebBeansContext.currentInstance();
+        final OpenWebBeansTestMetaDataDiscoveryService discoveryService = OpenWebBeansTestMetaDataDiscoveryService.class.cast(ctx.getScannerService());
+        discoveryService.deployClasses(asList(Service.class, ModelAdapter.class));
+        testLifecycle.startApplication(null);
+        try {
+            Jsonb jsonb = JsonbBuilder.create();
+            assertEquals("{\"model\":\"5\"}", jsonb.toJson(new Root(new Model(5))));
+            try {
+                AutoCloseable.class.cast(jsonb).close();
+            } catch (final Exception e) {
+                fail(e.getMessage());
+            }
+        } finally {
+            testLifecycle.stopApplication(null);
+        }
+    }
+
+    public static class Root {
+        @JsonbTypeAdapter(ModelAdapter.class)
+        public final Model model;
+
+        public Root(final Model model) {
+            this.model = model;
+        }
+    }
+
+    public static class Model {
+        private final int val;
+
+        public Model(final int i) {
+            val = i;
+        }
+    }
+
+    @ApplicationScoped
+    public static class Service {
+        public String toString(final Model model) {
+            return Integer.toString(model.val);
+        }
+    }
+
+    @ApplicationScoped
+    public static class ModelAdapter implements JsonbAdapter<String, Model> {
+        @Inject
+        private Service service;
+
+        @Override
+        public String adaptTo(final Model obj) throws Exception {
+            assertTrue(OwbNormalScopeProxy.class.isInstance(service)); // additional test
+            return service.toString(obj);
+        }
+
+        @Override
+        public Model adaptFrom(final String obj) throws Exception {
+            throw new UnsupportedOperationException();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/d271dec4/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java
index ea83813..dbeab9a 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java
@@ -37,6 +37,7 @@ import javax.json.JsonValue.ValueType;
 import javax.json.stream.JsonGenerator;
 import javax.json.stream.JsonGeneratorFactory;
 import javax.xml.bind.DatatypeConverter;
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -80,7 +81,7 @@ import java.util.concurrent.ConcurrentMap;
 
 import static java.util.Arrays.asList;
 
-public class Mapper {
+public class Mapper implements Closeable {
     private static final Converter<Object> FALLBACK_CONVERTER = new FallbackConverter();
     private static final JohnzonParameterizedType ANY_LIST = new JohnzonParameterizedType(List.class, Object.class);
 
@@ -96,12 +97,14 @@ public class Mapper {
     protected final boolean treatByteArrayAsBase64URL;
     protected final Charset encoding;
     protected final ReaderHandler readerHandler;
+    protected final Collection<Closeable> closeables;
 
     // CHECKSTYLE:OFF
     public Mapper(final JsonReaderFactory readerFactory, final JsonGeneratorFactory generatorFactory,
                   final boolean doClose, final Map<Type, Converter<?>> converters,
                   final int version, final Comparator<String> attributeOrder, final boolean skipNull, final boolean skipEmptyArray,
-                  final AccessMode accessMode, final boolean treatByteArrayAsBase64, final boolean treatByteArrayAsBase64URL, final Charset encoding) {
+                  final AccessMode accessMode, final boolean treatByteArrayAsBase64, final boolean treatByteArrayAsBase64URL, final Charset encoding,
+                  final Collection<Closeable> closeables) {
     // CHECKSTYLE:ON
         this.readerFactory = readerFactory;
         this.generatorFactory = generatorFactory;
@@ -115,6 +118,7 @@ public class Mapper {
         this.treatByteArrayAsBase64URL = treatByteArrayAsBase64URL;
         this.encoding = encoding;
         this.readerHandler = ReaderHandler.create(readerFactory);
+        this.closeables = closeables;
     }
 
     private static JsonGenerator writePrimitives(final JsonGenerator generator, final Object value) {
@@ -977,6 +981,25 @@ public class Mapper {
         return array;
     }
 
+    @Override
+    public synchronized void close() {
+        Collection<Exception> errors = null;
+        for (final Closeable c : closeables) {
+            try {
+                c.close();
+            } catch (final IOException e) {
+                if (errors == null) {
+                    errors = new ArrayList<Exception>();
+                }
+                errors.add(e);
+            }
+        }
+        closeables.clear();
+        if (errors != null) {
+            throw new IllegalStateException(errors.toString());
+        }
+    }
+
     private static class FallbackConverter implements Converter<Object> {
         @Override
         public String toString(final Object instance) {

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/d271dec4/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
index 369c162..64a09ff 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
@@ -44,12 +44,15 @@ import javax.json.JsonReaderFactory;
 import javax.json.spi.JsonProvider;
 import javax.json.stream.JsonGenerator;
 import javax.json.stream.JsonGeneratorFactory;
+import java.io.Closeable;
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.net.URI;
 import java.net.URL;
 import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
@@ -106,6 +109,7 @@ public class MapperBuilder {
     private Charset encoding = Charset.forName(System.getProperty("johnzon.mapper.encoding", "UTF-8"));
     private boolean useGetterForCollections;
     private String accessModeName;
+    private final Collection<Closeable> closeables = new ArrayList<Closeable>();
 
     public Mapper build() {
         if (readerFactory == null || generatorFactory == null) {
@@ -160,7 +164,13 @@ public class MapperBuilder {
                 skipNull, skipEmptyArray,
                 accessMode,
                 treatByteArrayAsBase64, treatByteArrayAsBase64URL,
-                encoding);
+                encoding,
+                closeables);
+    }
+
+    public MapperBuilder addCloseable(final Closeable closeable) {
+        closeables.add(closeable);
+        return this;
     }
 
     public MapperBuilder setIgnoreFieldsForType(final Class<?> type, final String... fields) {