Initial Contribution

Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/examples/managed-client/README.MD b/examples/managed-client/README.MD
new file mode 100644
index 0000000..249eeb9
--- /dev/null
+++ b/examples/managed-client/README.MD
@@ -0,0 +1,81 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Managed Client Example
+======================
+
+This example demonstrates a simple usage of Jersey Managed Client
+feature.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path            | Resource class     | HTTP methods
+------------------- | ------------------ | --------------
+**_/public/a_**     | PublicResource     | GET
+**_/public/b_**     | PublicResource     | GET
+**_/internal/a_**   | InternalResource   | GET
+**_/internal/b_**   | InternalResource   | GET
+
+In the example, the requests to a *public resource* deployed on
+`/public/` path are forwarded to an *internal resource* (deployed on
+`/internal/` path) using injected
+[javax.ws.rs.client.WebTarget](https://jax-rs-spec.java.net/nonav/2.0/apidocs/javax/ws/rs/client/WebTarget.html)
+instances produced using 2 separate managed clients each of the managed
+clients using it's own custom configuration.
+
+An access to internal resource methods is guarded by a container request
+filter (`CustomHeaderFilter`) which rejects any request that does not
+contain expected custom header set to an expected value. In the example,
+the 2 managed clients (used by a public resource to access the methods
+on the internal resource) are configured with custom configurations,
+each containing a registration of a custom client request filter that is
+instructed to append a required custom header and value to every
+outgoing request. Only with managed client support working properly, the
+public resource is able to successfully retrieve data from the internal
+resource.
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+>     mvn clean package exec:java
+
+This deploys current example on the local host. You can then access WADL
+description of the deployed application at
+<http://localhost:8080/managed-client-webapp/application.wadl>.
+
+You can access public resource of this application using curl:
+
+>     curl -v -H "Accept: text/plain" http://localhost:8080/managed-client-webapp/public/a
+
+>     curl -v -H "Accept: text/plain" http://localhost:8080/managed-client-webapp/public/b
+
+In this example you should see the returned response message body
+contains "a" or "b" respectively upon successful invocation.
+
+You may also verify that access to internal resource is not possible
+without including a proper header in the request. First try to access
+the internal resource without any custom header:
+
+>     curl -v -H "Accept: text/plain" http://localhost:8080/managed-client-webapp/internal/a
+
+>     curl -v -H "Accept: text/plain" http://localhost:8080/managed-client-webapp/internal/b
+
+In both cases a `HTTP 403 Forbidden.` response is returned. Now lets try
+to access the resource once again, but this time we'll include also the
+expected custom headers:
+
+>     curl -v -H "Accept: text/plain" -H "custom-header:a" http://localhost:8080/managed-client-webapp/internal/a
+
+>     curl -v -H "Accept: text/plain" -H "custom-header:b" http://localhost:8080/managed-client-webapp/internal/b
+
+Finally, you should see the invocation succeeded and the returned response message body contains "a" or "b" respectively.
diff --git a/examples/managed-client/pom.xml b/examples/managed-client/pom.xml
new file mode 100644
index 0000000..ad922d4
--- /dev/null
+++ b/examples/managed-client/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.examples</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>managed-client</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-examples-managed-client</name>
+
+    <description>Jersey managed client example.</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-grizzly2-http</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.inject</groupId>
+            <artifactId>jersey-hk2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+            <artifactId>jersey-test-framework-provider-bundle</artifactId>
+            <type>pom</type>
+            <scope>test</scope>
+        </dependency>
+        <!-- uncomment to use Grizzly client -->
+        <!--dependency>
+            <groupId>org.glassfish.jersey.connectors</groupId>
+            <artifactId>jersey-grizzly-connector</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency-->
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>org.glassfish.jersey.examples.managedclient.App</mainClass>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>release</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-assembly-plugin</artifactId>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/App.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/App.java
new file mode 100644
index 0000000..dc23e6d
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/App.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Jersey programmatic managed client example application.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class App {
+
+    private static final URI BASE_URI = URI.create("http://localhost:8080/managedclient/");
+
+    public static void main(String[] args) {
+        try {
+            System.out.println("\"Managed Client\" Jersey Example App");
+
+            final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, create(), false);
+            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    server.shutdownNow();
+                }
+            }));
+            server.start();
+
+            System.out.println(String.format("Application started.\nTry out public endpoints:\n  %s%s\n  %s%s\n"
+                            + "Stop the application using CTRL+C",
+                    BASE_URI, "public/a",
+                    BASE_URI, "public/b"));
+
+            Thread.currentThread().join();
+        } catch (IOException | InterruptedException ex) {
+            Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+        }
+
+    }
+
+    /**
+     * Create JAX-RS application for the example.
+     *
+     * @return create JAX-RS application for the example.
+     */
+    public static ResourceConfig create() {
+        return new ResourceConfig(PublicResource.class, InternalResource.class, CustomHeaderFeature.class)
+                .property(ClientA.class.getName() + ".baseUri", BASE_URI.toString() + "internal");
+    }
+
+    public static class MyClientAConfig extends ClientConfig {
+
+        public MyClientAConfig() {
+            this.register(new CustomHeaderFilter("custom-header", "a"));
+        }
+    }
+
+    public static class MyClientBConfig extends ClientConfig {
+
+        public MyClientBConfig() {
+            this.register(new CustomHeaderFilter("custom-header", "b"));
+        }
+    }
+}
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/ClientA.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/ClientA.java
new file mode 100644
index 0000000..c67dd1e
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/ClientA.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.server.ClientBinding;
+
+/**
+ * Managed client configuration for client A.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@ClientBinding(configClass = App.MyClientAConfig.class)
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+public @interface ClientA {
+}
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/ClientB.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/ClientB.java
new file mode 100644
index 0000000..3813df9
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/ClientB.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.server.ClientBinding;
+
+/**
+ * Managed client configuration for client B.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@ClientBinding(configClass = App.MyClientBConfig.class)
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+public @interface ClientB {
+}
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFeature.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFeature.java
new file mode 100644
index 0000000..7c249a1
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFeature.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.ws.rs.container.DynamicFeature;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.FeatureContext;
+
+/**
+ *Dynamic feature that appends a properly configured {@link CustomHeaderFilter} instance
+ * to every method that is annotated with {@link Require &#64;Require} internal feature
+ * annotation.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class CustomHeaderFeature implements DynamicFeature {
+
+    /**
+     * A method annotation to be placed on those resource methods to which a validating
+     * {@link CustomHeaderFilter} instance should be added.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Documented
+    @Target(ElementType.METHOD)
+    public static @interface Require {
+        /**
+         * Expected custom header name to be validated by the {@link CustomHeaderFilter}.
+         */
+        public String headerName();
+
+        /**
+         * Expected custom header value to be validated by the {@link CustomHeaderFilter}.
+         */
+        public String headerValue();
+    }
+
+    @Override
+    public void configure(ResourceInfo resourceInfo, FeatureContext context) {
+        final Require va = resourceInfo.getResourceMethod().getAnnotation(Require.class);
+        if (va != null) {
+            context.register(new CustomHeaderFilter(va.headerName(), va.headerValue()));
+        }
+    }
+}
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFilter.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFilter.java
new file mode 100644
index 0000000..a10414b
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFilter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.io.IOException;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ * A filter for appending and validating custom headers.
+ * <p>
+ * On the client side, appends a new custom request header with a configured name and value to each outgoing request.
+ * </p>
+ * <p>
+ * On the server side, validates that each request has a custom header with a configured name and value.
+ * If the validation fails a HTTP 403 response is returned.
+ * </p>
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class CustomHeaderFilter implements ContainerRequestFilter, ClientRequestFilter {
+    private final String headerName;
+    private final String headerValue;
+
+    public CustomHeaderFilter(String headerName, String headerValue) {
+        if (headerName == null || headerValue == null) {
+            throw new IllegalArgumentException("Header name and value must not be null.");
+        }
+        this.headerName = headerName;
+        this.headerValue = headerValue;
+    }
+
+    @Override
+    public void filter(ContainerRequestContext ctx) throws IOException { // validate
+        if (!headerValue.equals(ctx.getHeaderString(headerName))) {
+            ctx.abortWith(Response.status(Response.Status.FORBIDDEN)
+                    .type(MediaType.TEXT_PLAIN)
+                    .entity(String.format("Expected header '%s' not present or value not equal to '%s'", headerName, headerValue))
+                    .build());
+        }
+    }
+
+    @Override
+    public void filter(ClientRequestContext ctx) throws IOException { // append
+        ctx.getHeaders().putSingle(headerName, headerValue);
+    }
+}
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/InternalResource.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/InternalResource.java
new file mode 100644
index 0000000..f715129
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/InternalResource.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+/**
+ * Internal resource accessed from the managed client resource.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("internal")
+public class InternalResource {
+
+    @GET
+    @Path("a")
+    @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "a")
+    public String getA() {
+        return "a";
+    }
+
+    @GET
+    @Path("b")
+    @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "b")
+    public String getB() {
+        return "b";
+    }
+}
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/PublicResource.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/PublicResource.java
new file mode 100644
index 0000000..3340c16
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/PublicResource.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.Uri;
+
+/**
+ * A resource that uses managed clients to retrieve values of internal
+ * resources 'A' and 'B', which are protected by a {@link CustomHeaderFilter}
+ * and require a specific custom header in a request to be set to a specific value.
+ * <p>
+ * Properly configured managed clients have a {@code CustomHeaderFilter} instance
+ * configured to insert the {@link CustomHeaderFeature.Require required} custom header
+ * with a proper value into the outgoing client requests.
+ * </p>
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("public")
+public class PublicResource {
+    @Uri("a") @ClientA // resolves to <base>/internal/a
+    private WebTarget targetA;
+
+    @GET
+    @Produces("text/plain")
+    @Path("a")
+    public String getTargetA() {
+        return targetA.request(MediaType.TEXT_PLAIN).get(String.class);
+    }
+
+    @GET
+    @Produces("text/plain")
+    @Path("b")
+    public Response getTargetB(@Uri("internal/b") @ClientB WebTarget targetB) {
+        return targetB.request(MediaType.TEXT_PLAIN).get();
+    }
+}
diff --git a/examples/managed-client/src/test/java/org/glassfish/jersey/examples/managedclient/ManagedClientTest.java b/examples/managed-client/src/test/java/org/glassfish/jersey/examples/managedclient/ManagedClientTest.java
new file mode 100644
index 0000000..566aedd
--- /dev/null
+++ b/examples/managed-client/src/test/java/org/glassfish/jersey/examples/managedclient/ManagedClientTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Jersey managed client example tests.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class ManagedClientTest extends JerseyTest {
+
+    @Override
+    protected ResourceConfig configure() {
+        // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+        // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+        // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.jdkhttp.JdkHttpServerTestContainerFactory
+        enable(TestProperties.LOG_TRAFFIC);
+        // enable(TestProperties.DUMP_ENTITY);
+
+        // overriding ClientA base Uri property for test purposes
+        return App.create().property(ClientA.class.getName() + ".baseUri", this.getBaseUri().toString() + "internal");
+    }
+
+//    Uncomment to use Grizzly async client
+//    @Override
+//    protected void configureClient(ClientConfig clientConfig) {
+//        clientConfig.connector(new GrizzlyConnector(clientConfig));
+//    }
+
+    /**
+     * Test that a connection via managed clients works properly.
+     *
+     * @throws Exception in case of test failure.
+     */
+    @Test
+    public void testManagedClient() throws Exception {
+        final WebTarget resource = target().path("public").path("{name}");
+        Response response;
+
+        response = resource.resolveTemplate("name", "a").request(MediaType.TEXT_PLAIN).get();
+        assertEquals(200, response.getStatus());
+        assertEquals("a", response.readEntity(String.class));
+
+        response = resource.resolveTemplate("name", "b").request(MediaType.TEXT_PLAIN).get();
+        assertEquals(200, response.getStatus());
+        assertEquals("b", response.readEntity(String.class));
+    }
+}