Support for JAX-RS 2.2's hasProperties method (#4117)

Potential performance improvement by directly accessing the key set's
retrieval method instead of pulling the actual property value might be
slightly faster in some cases.

Signed-off-by: Markus KARG <markus@headcrashing.eu>
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java
index ddb1089..b78d0d6 100644
--- a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -133,6 +133,11 @@
                     private final Map<String, Object> properties = new HashMap<>();
 
                     @Override
+                    public boolean hasProperty(final String name) {
+                        return properties.containsKey(name);
+                    }
+
+                    @Override
                     public Object getProperty(String name) {
                         return properties.get(name);
                     }
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java
index 33437e2..9905afa 100644
--- a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -127,6 +127,11 @@
                     private final Map<String, Object> properties = new HashMap<>();
 
                     @Override
+                    public boolean hasProperty(final String name) {
+                        return properties.containsKey(name);
+                    }
+
+                    @Override
                     public Object getProperty(String name) {
                         return properties.get(name);
                     }
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java
index fb2ea46..a981b76 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -325,6 +325,11 @@
         }
 
         @Override
+        public boolean hasProperty(final String name) {
+            return commonConfig.getConfiguration().hasProperty(name);
+        }
+
+        @Override
         public Object getProperty(final String name) {
             return commonConfig.getConfiguration().getProperty(name);
         }
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java
index b58cbb5..154ed69 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -180,6 +180,11 @@
     }
 
     @Override
+    public boolean hasProperty(final String name) {
+        return propertiesDelegate.hasProperty(name);
+    }
+
+    @Override
     public Object getProperty(final String name) {
         return propertiesDelegate.getProperty(name);
     }
diff --git a/core-client/src/test/java/org/glassfish/jersey/client/ClientConfigTest.java b/core-client/src/test/java/org/glassfish/jersey/client/ClientConfigTest.java
index 05234b1..d1b5f89 100644
--- a/core-client/src/test/java/org/glassfish/jersey/client/ClientConfigTest.java
+++ b/core-client/src/test/java/org/glassfish/jersey/client/ClientConfigTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -104,6 +104,13 @@
     }
 
     @Test
+    public void testHasProperty() {
+        ClientConfig instance = new ClientConfig().property("name", "value");
+        assertTrue(instance.hasProperty("name"));
+        assertFalse(instance.hasProperty("other"));
+    }
+
+    @Test
     public void testGetProperty() {
         ClientConfig instance = new ClientConfig().property("name", "value");
         assertEquals("value", instance.getProperty("name"));
diff --git a/core-client/src/test/java/org/glassfish/jersey/client/ClientRequestTest.java b/core-client/src/test/java/org/glassfish/jersey/client/ClientRequestTest.java
index 77512ca..6e47a41 100644
--- a/core-client/src/test/java/org/glassfish/jersey/client/ClientRequestTest.java
+++ b/core-client/src/test/java/org/glassfish/jersey/client/ClientRequestTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -85,6 +85,9 @@
         assertFalse(request.getConfiguration().getPropertyNames().contains("name"));
         assertFalse(request.getPropertyNames().contains("name"));
 
+        assertFalse(request.getConfiguration().hasProperty("name"));
+        assertFalse(request.hasProperty("name"));
+
         assertNull(request.getConfiguration().getProperty("name"));
         assertNull(request.getProperty("name"));
 
@@ -102,6 +105,9 @@
         assertTrue(request.getConfiguration().getPropertyNames().contains("name"));
         assertFalse(request.getPropertyNames().contains("name"));
 
+        assertTrue(request.getConfiguration().hasProperty("name"));
+        assertFalse(request.hasProperty("name"));
+
         assertEquals("value-global", request.getConfiguration().getProperty("name"));
         assertNull(request.getProperty("name"));
 
@@ -120,6 +126,9 @@
         assertFalse(request.getConfiguration().getPropertyNames().contains("name"));
         assertTrue(request.getPropertyNames().contains("name"));
 
+        assertFalse(request.getConfiguration().hasProperty("name"));
+        assertTrue(request.hasProperty("name"));
+
         assertNull(request.getConfiguration().getProperty("name"));
         assertEquals("value-request", request.getProperty("name"));
 
@@ -138,6 +147,9 @@
         assertTrue(request.getConfiguration().getPropertyNames().contains("name"));
         assertTrue(request.getPropertyNames().contains("name"));
 
+        assertTrue(request.getConfiguration().hasProperty("name"));
+        assertTrue(request.hasProperty("name"));
+
         assertEquals("value-global", request.getConfiguration().getProperty("name"));
         assertEquals("value-request", request.getProperty("name"));
 
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/MapPropertiesDelegate.java b/core-common/src/main/java/org/glassfish/jersey/internal/MapPropertiesDelegate.java
index a1f8113..5da1992 100644
--- a/core-common/src/main/java/org/glassfish/jersey/internal/MapPropertiesDelegate.java
+++ b/core-common/src/main/java/org/glassfish/jersey/internal/MapPropertiesDelegate.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -64,6 +64,11 @@
     }
 
     @Override
+    public boolean hasProperty(final String name) {
+        return store.containsKey(name);
+    }
+
+    @Override
     public Object getProperty(String name) {
         return store.get(name);
     }
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/PropertiesDelegate.java b/core-common/src/main/java/org/glassfish/jersey/internal/PropertiesDelegate.java
index d0cde50..1d89e28 100644
--- a/core-common/src/main/java/org/glassfish/jersey/internal/PropertiesDelegate.java
+++ b/core-common/src/main/java/org/glassfish/jersey/internal/PropertiesDelegate.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -43,6 +43,21 @@
      */
     public Object getProperty(String name);
 
+    /**
+     * Returns {@code true} if the property with the given name registered in the current request/response
+     * exchange context, or {@code false} if there is no property by that name.
+     * <p>
+     * Use the {@link #getProperty} method with a property name to get the value of
+     * a property.
+     * </p>
+     *
+     * @return {@code true} if a property matching the given name exists, or
+     *         {@code false} otherwise.
+     * @see #getProperty
+     */
+    public default boolean hasProperty(String name) {
+        return getProperty(name) != null;
+    }
 
     /**
      * Returns an immutable {@link java.util.Collection collection} containing the property
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/InterceptorExecutor.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/InterceptorExecutor.java
index a3c6ff7..f40a816 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/InterceptorExecutor.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/InterceptorExecutor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -89,6 +89,11 @@
     }
 
     @Override
+    public boolean hasProperty(final String name) {
+        return propertiesDelegate.hasProperty(name);
+    }
+
+    @Override
     public Object getProperty(final String name) {
         return propertiesDelegate.getProperty(name);
     }
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/TracingAwarePropertiesDelegate.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/TracingAwarePropertiesDelegate.java
index 5824122..197f488 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/TracingAwarePropertiesDelegate.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/TracingAwarePropertiesDelegate.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -59,6 +59,14 @@
     }
 
     @Override
+    public boolean hasProperty(final String name) {
+        if (tracingLogger != null && TracingLogger.PROPERTY_NAME.equals(name)) {
+            return true;
+        }
+        return propertiesDelegate.hasProperty(name);
+    }
+
+    @Override
     public Object getProperty(String name) {
         if (tracingLogger != null && TracingLogger.PROPERTY_NAME.equals(name)) {
             return tracingLogger;
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ContainerRequest.java b/core-server/src/main/java/org/glassfish/jersey/server/ContainerRequest.java
index adb86aa..9308311 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/ContainerRequest.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/ContainerRequest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -248,6 +248,11 @@
     }
 
     @Override
+    public boolean hasProperty(final String name) {
+        return propertiesDelegate.hasProperty(name);
+    }
+
+    @Override
     public Object getProperty(final String name) {
         return propertiesDelegate.getProperty(name);
     }
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ResourceConfig.java b/core-server/src/main/java/org/glassfish/jersey/server/ResourceConfig.java
index 2560fd2..b0f9832 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/ResourceConfig.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/ResourceConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -728,6 +728,11 @@
     }
 
     @Override
+    public final boolean hasProperty(final String name) {
+        return state.hasProperty(name);
+    }
+
+    @Override
     public final Object getProperty(final String name) {
         return state.getProperty(name);
     }
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java
index 82ab90b..7662a25 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java
@@ -120,7 +120,17 @@
 
             @Override
             public final JAXRS.Configuration build() {
-                return this.properties::get;
+                return new JAXRS.Configuration() {
+                    @Override
+                    public final boolean hasProperty(final String name) {
+                        return properties.containsKey(name);
+                    }
+
+                    @Override
+                    public final Object property(final String name) {
+                        return properties.get(name);
+                    }
+                };
             }
         };
     }
@@ -138,14 +148,28 @@
 
                 @Override
                 public final Configuration configuration() {
-                    return name -> {
-                        switch (name) {
-                        case JAXRS.Configuration.PORT:
-                            return server.port();
-                        case ServerProperties.HTTP_SERVER_CLASS:
-                            return server.getClass();
-                        default:
-                            return configuration.property(name);
+                    return new Configuration() {
+                        @Override
+                        public final boolean hasProperty(final String name) {
+                            switch (name) {
+                            case JAXRS.Configuration.PORT:
+                            case ServerProperties.HTTP_SERVER_CLASS:
+                                return true;
+                            default:
+                                return configuration.hasProperty(name);
+                            }
+                        }
+
+                        @Override
+                        public final Object property(final String name) {
+                            switch (name) {
+                            case JAXRS.Configuration.PORT:
+                                return server.port();
+                            case ServerProperties.HTTP_SERVER_CLASS:
+                                return server.getClass();
+                            default:
+                                return configuration.property(name);
+                            }
                         }
                     };
                 }
diff --git a/core-server/src/test/java/org/glassfish/jersey/server/internal/RuntimeDelegateImplTest.java b/core-server/src/test/java/org/glassfish/jersey/server/internal/RuntimeDelegateImplTest.java
index 2af3eb2..36f60a2 100644
--- a/core-server/src/test/java/org/glassfish/jersey/server/internal/RuntimeDelegateImplTest.java
+++ b/core-server/src/test/java/org/glassfish/jersey/server/internal/RuntimeDelegateImplTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -17,6 +17,7 @@
 package org.glassfish.jersey.server.internal;
 
 import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
@@ -41,6 +42,8 @@
 import javax.ws.rs.core.Application;
 import javax.ws.rs.ext.RuntimeDelegate;
 
+import org.hamcrest.FeatureMatcher;
+import org.hamcrest.Matcher;
 import org.glassfish.jersey.internal.ServiceFinder;
 import org.glassfish.jersey.internal.ServiceFinder.ServiceIteratorProvider;
 import org.glassfish.jersey.server.ServerProperties;
@@ -106,6 +109,12 @@
 
         // then
         assertThat(configuration, is(notNullValue()));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.PROTOCOL));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.HOST));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.PORT));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.ROOT_PATH));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.SSL_CONTEXT));
         assertThat(configuration.property(JAXRS.Configuration.PROTOCOL), is("HTTP"));
         assertThat(configuration.property(JAXRS.Configuration.HOST), is("localhost"));
         assertThat(configuration.property(JAXRS.Configuration.PORT), is(JAXRS.Configuration.DEFAULT_PORT));
@@ -131,6 +140,7 @@
 
         // then
         assertThat(configuration, is(notNullValue()));
+        assertThat(configuration, hasProperty("property"));
         assertThat(configuration.property("property"), is("value"));
     }
 
@@ -150,6 +160,12 @@
 
         // then
         assertThat(configuration, is(notNullValue()));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.PROTOCOL));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.HOST));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.PORT));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.ROOT_PATH));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.SSL_CONTEXT));
         assertThat(configuration.protocol(), is("HTTPS"));
         assertThat(configuration.host(), is("hostname"));
         assertThat(configuration.port(), is(8080));
@@ -171,6 +187,12 @@
 
         // then
         assertThat(configuration, is(notNullValue()));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.PROTOCOL));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.HOST));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.PORT));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.ROOT_PATH));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.SSL_CONTEXT));
         assertThat(configuration.protocol(), is("HTTPS"));
         assertThat(configuration.host(), is("hostname"));
         assertThat(configuration.port(), is(8080));
@@ -218,6 +240,14 @@
 
         // then
         assertThat(configuration, is(notNullValue()));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.PROTOCOL));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.HOST));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.PORT));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.ROOT_PATH));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.SSL_CONTEXT));
+        assertThat(configuration, hasProperty(ServerProperties.HTTP_SERVER_CLASS));
+        assertThat(configuration, hasProperty(ServerProperties.AUTO_START));
         assertThat(configuration.protocol(), is("HTTPS"));
         assertThat(configuration.host(), is("hostname"));
         assertThat(configuration.port(), is(8080));
@@ -315,6 +345,13 @@
         // then
         assertThat(instance, is(notNullValue()));
         assertThat(configuration, is(notNullValue()));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.PROTOCOL));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.HOST));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.PORT));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.ROOT_PATH));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION));
+        assertThat(configuration, hasProperty(JAXRS.Configuration.SSL_CONTEXT));
+        assertThat(configuration, hasProperty(ServerProperties.HTTP_SERVER_CLASS));
         assertThat(configuration.protocol(), is("HTTPS"));
         assertThat(configuration.host(), is("hostname"));
         assertThat(configuration.port(), is(8888));
@@ -332,4 +369,21 @@
         ServiceFinder.setIteratorProvider(null);
     }
 
+   /**
+    * Creates a matcher that matches any examined object whose <code>hasProperty</code> method
+    * returns {@code true} for the provided property name.
+    * For example:
+    * <pre>assertThat(configuration, hasProperty("HOST"))</pre>
+    *
+    * @param propertyName
+    *     the property name to check
+    */
+    private static final Matcher<Configuration> hasProperty(final String propertyName) {
+        return new FeatureMatcher<Configuration, Boolean>(is(TRUE), "hasProperty", "hasProperty") {
+            @Override
+            protected final Boolean featureValueOf(final Configuration actual) {
+                return actual.hasProperty(propertyName);
+            }
+        };
+    }
 }