[jackson-jaxrs-providers] 136/162: Implement #49, addition of `JaxRSFeature.ALLOW_EMPTY_INPUT`

Timo Aaltonen tjaalton at moszumanska.debian.org
Mon Sep 8 22:16:36 UTC 2014


This is an automated email from the git hooks/post-receive script.

tjaalton pushed a commit to branch master
in repository jackson-jaxrs-providers.

commit 772ef5b6f51a4ac73fa3469618c6db9b97b5619b
Author: Tatu Saloranta <tatu.saloranta at iki.fi>
Date:   Sun Apr 6 17:15:03 2014 -0700

    Implement #49, addition of `JaxRSFeature.ALLOW_EMPTY_INPUT`
---
 .../fasterxml/jackson/jaxrs/base/ProviderBase.java | 72 +++++++++++++++++-----
 .../fasterxml/jackson/jaxrs/cfg/JaxRSFeature.java  | 28 +++++++++
 .../jackson/jaxrs/json/TestCanDeserialize.java     | 44 +++++++++++--
 pom.xml                                            |  6 ++
 release-notes/VERSION                              | 13 ++--
 5 files changed, 138 insertions(+), 25 deletions(-)

diff --git a/base/src/main/java/com/fasterxml/jackson/jaxrs/base/ProviderBase.java b/base/src/main/java/com/fasterxml/jackson/jaxrs/base/ProviderBase.java
index 6ebacf7..75653e2 100644
--- a/base/src/main/java/com/fasterxml/jackson/jaxrs/base/ProviderBase.java
+++ b/base/src/main/java/com/fasterxml/jackson/jaxrs/base/ProviderBase.java
@@ -2,8 +2,10 @@ package com.fasterxml.jackson.jaxrs.base;
 
 import java.io.*;
 import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Type;
 import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
 
 import javax.ws.rs.core.*;
 import javax.ws.rs.ext.MessageBodyReader;
@@ -32,6 +34,15 @@ public abstract class ProviderBase<
     public final static String HEADER_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options";
 
     /**
+     * Since class <code>javax.ws.rs.core.NoContentException</code> only exists in
+     * JAX-RS 2.0, but we need 1.1 compatibility, need to (unfortunately!) dynamically
+     * load class.
+     */
+    protected final static String CLASS_NAME_NO_CONTENT_EXCEPTION = "javax.ws.rs.core.NoContentException";
+
+    protected final static String NO_CONTENT_MESSAGE = "No content (empty input stream)";
+    
+    /**
      * Looks like we need to worry about accidental
      *   data binding for types we shouldn't be handling. This is
      *   probably not a very good way to do it, but let's start by
@@ -77,6 +88,8 @@ public abstract class ProviderBase<
         StreamingOutput.class, Response.class
     };
 
+    protected final static int JAXRS_FEATURE_DEFAULTS = JaxRSFeature.collectDefaults();
+    
     /*
     /**********************************************************
     /* General configuration
@@ -84,6 +97,12 @@ public abstract class ProviderBase<
      */
 
     /**
+     * Helper object used for encapsulating configuration aspects
+     * of {@link ObjectMapper}
+     */
+    protected final MAPPER_CONFIG _mapperConfig;
+    
+    /**
      * Map that contains overrides to default list of untouchable
      * types: <code>true</code> meaning that entry is untouchable,
      * <code>false</code> that is is not.
@@ -128,7 +147,7 @@ public abstract class ProviderBase<
     /* Excluded types
     /**********************************************************
      */
-    
+
     public final static HashSet<ClassKey> _untouchables = DEFAULT_UNTOUCHABLES;
 
     public final static Class<?>[] _unreadableClasses = DEFAULT_UNREADABLES;
@@ -152,27 +171,19 @@ public abstract class ProviderBase<
      */
     protected final LRUMap<AnnotationBundleKey, EP_CONFIG> _writers
         = new LRUMap<AnnotationBundleKey, EP_CONFIG>(16, 120);
-    
-    /*
-    /**********************************************************
-    /* General configuration
-    /**********************************************************
-     */
-    
-    /**
-     * Helper object used for encapsulating configuration aspects
-     * of {@link ObjectMapper}
-     */
-    protected final MAPPER_CONFIG _mapperConfig;
+
+    protected final AtomicReference<IOException> _noContentExceptionRef
+        = new AtomicReference<IOException>();
 
     /*
     /**********************************************************
     /* Life-cycle
     /**********************************************************
      */
-    
+
     protected ProviderBase(MAPPER_CONFIG mconfig) {
         _mapperConfig = mconfig;
+        _jaxRSFeatures = JAXRS_FEATURE_DEFAULTS;
     }
 
     /**
@@ -182,9 +193,10 @@ public abstract class ProviderBase<
      * Should NOT be used by any code explicitly; only exists
      * for proxy support.
      */
-    @Deprecated // just to denote it should NOT be directly called; will not be removed
+    @Deprecated // just to denote it should NOT be directly called; will NOT be removed
     protected ProviderBase() {
         _mapperConfig = null;
+        _jaxRSFeatures = JAXRS_FEATURE_DEFAULTS;
     }
     
     /*
@@ -760,8 +772,19 @@ public abstract class ProviderBase<
         JsonParser jp = _createParser(reader, entityStream);
         
         // If null is returned, considered to be empty stream
+        // 05-Apr-2014, tatu: As per [Issue#49], behavior here is configurable.
         if (jp == null || jp.nextToken() == null) {
-            return null;
+            if (JaxRSFeature.ALLOW_EMPTY_INPUT.enabledIn(_jaxRSFeatures)) {
+                return null;
+            }
+            /* 05-Apr-2014, tatu: Trick-ee. NoContentFoundException only available in JAX-RS 2.0...
+             *   so need bit of obfuscated code to reach it.
+             */
+            IOException fail = _noContentExceptionRef.get();
+            if (fail == null) {
+                fail = _createNoContentException();
+            }
+            throw fail;
         }
         // [Issue#1]: allow 'binding' to JsonParser
         if (((Class<?>) type) == JsonParser.class) {
@@ -846,6 +869,23 @@ public abstract class ProviderBase<
         return JsonParser.class == type;
     }
 
+    /**
+     * @since 2.4
+     */
+    protected IOException _createNoContentException()
+    {
+        Class<?> cls = null;
+        try {
+            cls = Class.forName(CLASS_NAME_NO_CONTENT_EXCEPTION);
+            Constructor<?> ctor = cls.getDeclaredConstructor(String.class);
+            if (ctor != null) {
+                return (IOException) ctor.newInstance(NO_CONTENT_MESSAGE);
+            }
+        } catch (Exception e) { // no can do...
+        }
+        return new IOException(NO_CONTENT_MESSAGE);
+    }
+    
     /*
     /**********************************************************
     /* Private/sub-class helper methods
diff --git a/base/src/main/java/com/fasterxml/jackson/jaxrs/cfg/JaxRSFeature.java b/base/src/main/java/com/fasterxml/jackson/jaxrs/cfg/JaxRSFeature.java
index 9e415fb..c351ca6 100644
--- a/base/src/main/java/com/fasterxml/jackson/jaxrs/cfg/JaxRSFeature.java
+++ b/base/src/main/java/com/fasterxml/jackson/jaxrs/cfg/JaxRSFeature.java
@@ -11,6 +11,24 @@ public enum JaxRSFeature implements ConfigFeature
 {
     /*
     /******************************************************
+    /* Input handling
+    /******************************************************
+     */
+
+    /**
+     * Feature related to
+     * <a href="https://github.com/FasterXML/jackson-jaxrs-providers/issues/49">Issue #49</a>:
+     * whether empty input is considered legal or not.
+     * If set to true, empty content is allowed and will be read as Java 'null': if false,
+     * an {@link java.io.IOException} will be thrown.
+     *<p>
+     * NOTE: in case of JAX-RS 2.0, specific exception will be <code>javax.ws.rs.core.NoContentException</code>;
+     * but this is not defined in JAX-RS 1.x.
+     */
+    ALLOW_EMPTY_INPUT(true),
+    
+    /*
+    /******************************************************
     /* HTTP headers
     /******************************************************
      */
@@ -39,9 +57,19 @@ public enum JaxRSFeature implements ConfigFeature
         _defaultState = defaultState;
     }
 
+    public static int collectDefaults() {
+        int flags = 0;
+        for (JaxRSFeature f : values()) {
+            if (f.enabledByDefault()) { flags |= f.getMask(); }
+        }
+        return flags;
+    }
+    
     @Override
     public boolean enabledByDefault() { return _defaultState; }
 
     @Override
     public int getMask() { return (1 << ordinal()); }
+
+    public boolean enabledIn(int flags) { return (flags & getMask()) != 0; }    
 }
diff --git a/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestCanDeserialize.java b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestCanDeserialize.java
index dafb45c..5da49e2 100644
--- a/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestCanDeserialize.java
+++ b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestCanDeserialize.java
@@ -9,13 +9,15 @@ import java.util.Map;
 
 import javax.ws.rs.core.MediaType;
 
+import com.fasterxml.jackson.jaxrs.cfg.JaxRSFeature;
+
 /**
  * Unit test to check [JACKSON-540]
  */
 public class TestCanDeserialize extends JaxrsTestBase {
 
 	@SuppressWarnings({ "unchecked", "rawtypes" })
-	public void testCanSerialize() throws IOException {
+	public void testCanDeserialize() throws IOException {
 		Map<String, Object> object = new LinkedHashMap<String, Object>();
 		JacksonJsonProvider prov = new JacksonJsonProvider();
 
@@ -29,15 +31,47 @@ public class TestCanDeserialize extends JaxrsTestBase {
 	}
 
 	@SuppressWarnings({ "unchecked", "rawtypes" })
-	public void testCanSerializeEmpty() throws IOException {
-		Map<String, Object> object = new LinkedHashMap<String, Object>();
+	public void testCanDeserializeEmpty() throws IOException {
 		JacksonJsonProvider prov = new JacksonJsonProvider();
 
 		InputStream stream = new ByteArrayInputStream(new byte[0]);
+		Class<Object> type = _type(Map.class);
 
-		object = (Map) prov.readFrom(Object.class, object.getClass(), new Annotation[0],
+          Map<String, Object> result = (Map) prov.readFrom(type, type, new Annotation[0],
 				MediaType.APPLICATION_JSON_TYPE, null, stream);
 		
-		assertNull(object);
+		assertNull(result);
 	}
+
+	/**
+	 * Unit test for verifying functioning of {@link JaxRSFeature#ALLOW_EMPTY_INPUT}.
+	 */
+     public void testFailingDeserializeEmpty() throws IOException {
+         JacksonJsonProvider prov = new JacksonJsonProvider();
+         prov.disable(JaxRSFeature.ALLOW_EMPTY_INPUT);
+
+         InputStream stream = new ByteArrayInputStream(new byte[0]);
+         Class<Object> type = _type(Map.class);
+         try {
+             prov.readFrom(type, type, new Annotation[0],
+                   MediaType.APPLICATION_JSON_TYPE, null, stream);
+             fail("Should not succeed with passing of empty input");
+         } catch (IOException e) {
+             verifyException(e, "no content");
+             
+             final String clsName = e.getClass().getName();
+             if ("javax.ws.rs.core.NoContentException".equals(clsName)) {
+                 // Ideally, we'd get this
+             } else if (e.getClass() == IOException.class) {
+                 // but for JAX-RS 1.x this'll do
+             } else {
+                 fail("Unexpected exception type: "+clsName);
+             }
+         }
+    }
+
+    @SuppressWarnings("unchecked")
+    private Class<Object> _type(Class<?> cls) {
+        return (Class<Object>) cls;
+    }
 }
diff --git a/pom.xml b/pom.xml
index 4390919..571b01c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,8 +62,14 @@
        -->
     <dependency>
       <groupId>javax.ws.rs</groupId>
+      <!-- 05-Apr-2014, tatu: JAX-RS 2.x has different artifact-id, "javax.ws.rs-api"
+        -->
       <artifactId>jsr311-api</artifactId>
       <version>1.1.1</version>
+<!--
+      <artifactId>javax.ws.rs-api</artifactId>
+      <version>2.0</version>
+-->
       <scope>provided</scope>
     </dependency>
 
diff --git a/release-notes/VERSION b/release-notes/VERSION
index d4e9c47..a37b92b 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -5,16 +5,21 @@ Sub-modules:
   jackson-jaxrs-smile-provider
   jackson-jaxrs-xml-provider
 
-2.3.3 (xx-xxx-2013)
+2.4.0 (xx-xxx-2014)
 
-#41: Try to resolve problems with RESTeasy, missing `_configForWriting`
-  override.
- (reported by `tbroyer at github`)
+#49: Add `JaxRSFeature.ALLOW_EMPTY_INPUT`, disabling of which can prevent
+  mapping of empty input into Java null value
 
 ------------------------------------------------------------------------
 === History: ===
 ------------------------------------------------------------------------
 
+2.3.3 (xx-xxx-2014)
+
+#41: Try to resolve problems with RESTeasy, missing `_configForWriting`
+  override.
+ (reported by `tbroyer at github`)
+
 2.3.2 (01-Mar-2014)
 
 #40: Allow use of "text/x-json" content type by default

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/jackson-jaxrs-providers.git



More information about the pkg-java-commits mailing list