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 @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)); + } +}