+ * {@link RemoteEJBContextProvider} is used, which is a test artifact abstracting the different
+ * ways this is done for different servers.
+ *
+ * @author Arjan Tijms
+ *
+ */
+@RunWith(Arquillian.class)
+public class RemoteBeanTest {
+
+ private RemoteEJBContextProvider remoteEJBContextProvider;
+
+ @Deployment
+ public static Archive> deployment() {
+
+ // Add user u1 with password p1 and group g1 to the container's native identity store
+ addUsersToContainerIdentityStore();
+
+ return ShrinkWrap.create(JavaArchive.class)
+ .addClasses(Bean.class, BeanRemote.class)
+ .addAsManifestResource(INSTANCE, "beans.xml");
+ }
+
+ @Before
+ public void before() {
+ remoteEJBContextProvider = RemoteEJBContextFactory.getProvider();
+ assumeTrue(
+ "No RemoteEJBContextProvider available in current profile",
+ remoteEJBContextProvider != null);
+ }
+
+ @After
+ public void after() {
+ remoteEJBContextProvider.releaseContext();
+ }
+
+ @Test
+ @RunAsClient
+ public void callProtectedRemoteBean() throws NamingException {
+ // Obtain the JNDI naming context in a vendor specific way.
+ Context ejbRemoteContext = remoteEJBContextProvider.getContextWithCredentialsSet("u1", "p1");
+
+ BeanRemote beanRemote = (BeanRemote) ejbRemoteContext.lookup("java:global/test/Bean");
+
+ assertEquals("method", beanRemote.method());
+ }
+
+}
\ No newline at end of file
diff --git a/ejb/remote/roles-allowed/src/test/resources/addUsersPayara.txt b/ejb/remote/roles-allowed/src/test/resources/addUsersPayara.txt
new file mode 100644
index 00000000..037cdbd6
--- /dev/null
+++ b/ejb/remote/roles-allowed/src/test/resources/addUsersPayara.txt
@@ -0,0 +1 @@
+create-file-user --groups g1 --passwordfile ${project.build.directory}/test-classes/password.txt u1
\ No newline at end of file
diff --git a/ejb/remote/roles-allowed/src/test/resources/password.txt b/ejb/remote/roles-allowed/src/test/resources/password.txt
new file mode 100644
index 00000000..c00bb4ca
--- /dev/null
+++ b/ejb/remote/roles-allowed/src/test/resources/password.txt
@@ -0,0 +1 @@
+AS_ADMIN_USERPASSWORD=p1
diff --git a/ejb/remote/vendor/README.md b/ejb/remote/vendor/README.md
new file mode 100644
index 00000000..7295bd08
--- /dev/null
+++ b/ejb/remote/vendor/README.md
@@ -0,0 +1,12 @@
+# Java EE 8 Samples: EJB - Remote - Vendor #
+
+This module contains vendor specific implementations to obtain the JNDI context from where remote EJB beans can be requested
+from with a username/password credential.
+
+## Implementations ##
+
+ - payara-glassfish - An implementation that works for both Payara and GlassFish
+
+
+
+
diff --git a/ejb/remote/vendor/payara-rest/README.md b/ejb/remote/vendor/payara-rest/README.md
new file mode 100644
index 00000000..b1a3a57b
--- /dev/null
+++ b/ejb/remote/vendor/payara-rest/README.md
@@ -0,0 +1,9 @@
+# Java EE 7 Samples: EJB - Remote - Vendor - Payara and GlassFish #
+
+This modules contains a class that returns a JNDI context suitable for remote lookups against the default URL
+for a remote Payara or GlassFish server (localhost). It sets the provided credentials
+in a Payara/GlassFish specific way and puts the required client jar on the classpath.
+
+
+
+
diff --git a/ejb/remote/vendor/payara-rest/pom.xml b/ejb/remote/vendor/payara-rest/pom.xml
new file mode 100644
index 00000000..e29fc862
--- /dev/null
+++ b/ejb/remote/vendor/payara-rest/pom.xml
@@ -0,0 +1,30 @@
+
+
+
+
+4.0.0
+
+
+ org.javaee8
+ ejb-remote-vendor
+ 1.0-SNAPSHOT
+
+
+ org.javaee8.ejb.remote.vendor
+ ejb.remote.vendor.payara-rest
+
+ Java EE 8 Sample: ejb - remote - vendor - Payara REST Remote EJB Provider
+
+
+
+ org.javaee8
+ test-utils
+ 1.0-SNAPSHOT
+
+
+ fish.payara.extras
+ ejb-http-client
+ 5.191
+
+
+
diff --git a/ejb/remote/vendor/payara-rest/src/main/java/org/javaee8/PayaraEJBContextProvider.java b/ejb/remote/vendor/payara-rest/src/main/java/org/javaee8/PayaraEJBContextProvider.java
new file mode 100644
index 00000000..a1414863
--- /dev/null
+++ b/ejb/remote/vendor/payara-rest/src/main/java/org/javaee8/PayaraEJBContextProvider.java
@@ -0,0 +1,42 @@
+/** Copyright Payara Services Limited **/
+package org.javaee8;
+
+import static javax.naming.Context.INITIAL_CONTEXT_FACTORY;
+import static javax.naming.Context.PROVIDER_URL;
+import static javax.naming.Context.SECURITY_CREDENTIALS;
+import static javax.naming.Context.SECURITY_PRINCIPAL;
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+/**
+ * This class returns a JNDI context suitable for remote lookups against the default URL
+ * for a remote Payara or GlassFish server (localhost). It sets the provided credentials
+ * in a Payara/GlassFish specific way.
+ *
+ * @author Arjan Tijms
+ *
+ */
+public class PayaraEJBContextProvider implements RemoteEJBContextProvider {
+ public Context getContextWithCredentialsSet(String username, String password) {
+ Hashtable environment = new Hashtable();
+ environment.put(INITIAL_CONTEXT_FACTORY, "fish.payara.ejb.rest.client.RemoteEJBContextFactory");
+ environment.put(PROVIDER_URL, "http://localhost:8080/ejb-invoker");
+ environment.put(SECURITY_PRINCIPAL, "u1");
+ environment.put(SECURITY_CREDENTIALS, "p1");
+
+ try {
+ return new InitialContext(environment);
+ } catch (NamingException e) {
+ throw new IllegalStateException(e);
+ }
+
+ }
+
+ public void releaseContext() {
+ }
+
+}
diff --git a/ejb/remote/vendor/payara-rest/src/main/resources/META-INF/services/org.javaee8.RemoteEJBContextProvider b/ejb/remote/vendor/payara-rest/src/main/resources/META-INF/services/org.javaee8.RemoteEJBContextProvider
new file mode 100644
index 00000000..039b7cb9
--- /dev/null
+++ b/ejb/remote/vendor/payara-rest/src/main/resources/META-INF/services/org.javaee8.RemoteEJBContextProvider
@@ -0,0 +1 @@
+org.javaee8.PayaraEJBContextProvider
\ No newline at end of file
diff --git a/ejb/remote/vendor/pom.xml b/ejb/remote/vendor/pom.xml
new file mode 100644
index 00000000..05f59217
--- /dev/null
+++ b/ejb/remote/vendor/pom.xml
@@ -0,0 +1,37 @@
+
+
+
+
+4.0.0
+
+ org.javaee8
+ ejb-remote-vendor
+ 1.0-SNAPSHOT
+ pom
+
+ Java EE 8 Sample: ejb - remote - vendor
+
+
+
+ payara-ci-managed
+
+ payara-rest
+
+
+
+
+ payara-remote
+
+ payara-rest
+
+
+
+
+ glassfish-remote
+
+ payara-rest
+
+
+
+
+
diff --git a/jaxrs/README.md b/jaxrs/README.md
new file mode 100644
index 00000000..d702d957
--- /dev/null
+++ b/jaxrs/README.md
@@ -0,0 +1,7 @@
+# Java EE 8 Samples: JAX-RS 2.1#
+
+The [JSR 370](https://www.jcp.org/en/jsr/detail?id=370) specifies the JavaTM API for RESTful Web Services.
+
+## Samples ##
+
+ - producer
\ No newline at end of file
diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
new file mode 100644
index 00000000..d0bb4e37
--- /dev/null
+++ b/jaxrs/pom.xml
@@ -0,0 +1,26 @@
+
+4.0.0
+
+
+ org.javaee8
+ samples-parent
+ 1.0-SNAPSHOT
+
+
+ jaxrs
+ pom
+
+ Java EE 8 Samples: JAX-RS
+
+
+ sse-producer
+
+
+
+
+ org.javaee8
+ test-utils
+ ${project.version}
+
+
+
diff --git a/jaxrs/sse-producer/pom.xml b/jaxrs/sse-producer/pom.xml
new file mode 100644
index 00000000..faabd965
--- /dev/null
+++ b/jaxrs/sse-producer/pom.xml
@@ -0,0 +1,13 @@
+
+4.0.0
+
+
+ org.javaee8
+ jaxrs
+ 1.0-SNAPSHOT
+
+
+ sse-producer
+ war
+ Java EE 8 Samples: JAX-RS - SSE Producer
+
diff --git a/jaxrs/sse-producer/src/main/java/org/javaee8/jaxrs/sseproducer/data/EventData.java b/jaxrs/sse-producer/src/main/java/org/javaee8/jaxrs/sseproducer/data/EventData.java
new file mode 100644
index 00000000..8c208462
--- /dev/null
+++ b/jaxrs/sse-producer/src/main/java/org/javaee8/jaxrs/sseproducer/data/EventData.java
@@ -0,0 +1,48 @@
+package org.javaee8.jaxrs.sseproducer.data;
+
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ *
+ * @author daniel
+ */
+public class EventData {
+
+ private Date time;
+ private String id;
+ private String comment;
+
+ public EventData() {
+ }
+
+ public EventData(String comment) {
+ this.setTime(new Date());
+ this.setId(UUID.randomUUID().toString());
+ this.setComment(comment);
+ }
+
+ public Date getTime() {
+ return time;
+ }
+
+ public void setTime(Date time) {
+ this.time = time;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getComment() {
+ return comment;
+ }
+
+ public void setComment(String comment) {
+ this.comment = comment;
+ }
+}
diff --git a/jaxrs/sse-producer/src/main/java/org/javaee8/jaxrs/sseproducer/producer/SseResource.java b/jaxrs/sse-producer/src/main/java/org/javaee8/jaxrs/sseproducer/producer/SseResource.java
new file mode 100644
index 00000000..b82a60de
--- /dev/null
+++ b/jaxrs/sse-producer/src/main/java/org/javaee8/jaxrs/sseproducer/producer/SseResource.java
@@ -0,0 +1,58 @@
+package org.javaee8.jaxrs.sseproducer.producer;
+
+import javax.annotation.PostConstruct;
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.sse.Sse;
+import javax.ws.rs.sse.SseBroadcaster;
+import javax.ws.rs.sse.SseEventSink;
+
+import org.javaee8.jaxrs.sseproducer.data.EventData;
+
+/**
+ * Produces server side events.
+ *
+ * @author Daniel Contreras
+ */
+@Path("sse")
+public class SseResource {
+
+ @Context
+ private Sse sse;
+
+ private volatile SseBroadcaster sseBroadcaster;
+
+ @PostConstruct
+ public void init() {
+ this.sseBroadcaster = sse.newBroadcaster();
+ }
+
+ @GET
+ @Path("register")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void register(@Context SseEventSink eventSink) {
+
+ final Jsonb json = JsonbBuilder.create();
+ eventSink.send(sse.newEvent("INIT", json.toJson(new EventData("event:intialized"))));
+
+ sseBroadcaster.register(eventSink);
+
+ for (int i = 0; i < 5; i++) {
+
+ sseBroadcaster.broadcast(sse.newEvent("EVENT", json.toJson(new EventData("event:" + i))));
+
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ eventSink.send(sse.newEvent("FINISH", json.toJson(new EventData("event:finished"))));
+ }
+}
diff --git a/jaxrs/sse-producer/src/main/java/org/javaee8/jaxrs/sseproducer/rest/RestApplication.java b/jaxrs/sse-producer/src/main/java/org/javaee8/jaxrs/sseproducer/rest/RestApplication.java
new file mode 100644
index 00000000..66e3d31d
--- /dev/null
+++ b/jaxrs/sse-producer/src/main/java/org/javaee8/jaxrs/sseproducer/rest/RestApplication.java
@@ -0,0 +1,13 @@
+package org.javaee8.jaxrs.sseproducer.rest;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+/**
+ *
+ * @author daniel
+ */
+@ApplicationPath("rest")
+public class RestApplication extends Application {
+
+}
diff --git a/jaxrs/sse-producer/src/main/webapp/index.html b/jaxrs/sse-producer/src/main/webapp/index.html
new file mode 100644
index 00000000..a7763133
--- /dev/null
+++ b/jaxrs/sse-producer/src/main/webapp/index.html
@@ -0,0 +1,43 @@
+
+
+
+
+ SSE
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jaxrs/sse-producer/src/test/java/org/javaee8/jaxrs/sseproducer/SseResourceTest.java b/jaxrs/sse-producer/src/test/java/org/javaee8/jaxrs/sseproducer/SseResourceTest.java
new file mode 100644
index 00000000..aa411b7a
--- /dev/null
+++ b/jaxrs/sse-producer/src/test/java/org/javaee8/jaxrs/sseproducer/SseResourceTest.java
@@ -0,0 +1,132 @@
+package org.javaee8.jaxrs.sseproducer;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.jboss.shrinkwrap.api.ShrinkWrap.create;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import java.net.URL;
+import java.util.Date;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.function.Consumer;
+
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.sse.InboundSseEvent;
+import javax.ws.rs.sse.SseEventSource;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.client.HttpUrlConnectorProvider;
+import org.hamcrest.Matchers;
+import org.javaee8.jaxrs.sseproducer.data.EventData;
+import org.javaee8.jaxrs.sseproducer.producer.SseResource;
+import org.javaee8.jaxrs.sseproducer.rest.RestApplication;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test example for the Server-Sent Events with the Jersey JAX-RS implementation.
+ *
+ * @author Daniel Contreras
+ * @author David Matějček
+ */
+@RunWith(Arquillian.class)
+public class SseResourceTest {
+
+ private static final String[] EVENT_TYPES = {"INIT", "EVENT", "FINISH"};
+
+ @ArquillianResource
+ private URL base;
+
+ private Client sseClient;
+ private WebTarget target;
+ private SseEventSource eventSource;
+
+ @Deployment(testable = true)
+ public static WebArchive createDeployment() {
+ return create(WebArchive.class).addClasses(RestApplication.class, SseResource.class, EventData.class);
+ }
+
+
+ /**
+ * Initializes the client, target and the eventSource used to create event consumers
+ */
+ @Before
+ public void setup() {
+ // this is needed to avoid a conflict with embedded server, that can have
+ // customized configuration and connector providers.
+ final ClientConfig configuration = new ClientConfig();
+ configuration.property(ClientProperties.CONNECT_TIMEOUT, 100);
+ configuration.property(ClientProperties.READ_TIMEOUT, 5000);
+ configuration.connectorProvider(new HttpUrlConnectorProvider());
+ this.sseClient = ClientBuilder.newClient(configuration);
+ this.target = this.sseClient.target(this.base + "rest/sse/register");
+ this.eventSource = SseEventSource.target(this.target).build();
+ System.out.println("SSE Event source created........");
+ final Response response = this.target.request().get();
+ assertThat("GET response status - server is not ready", response.getStatus(),
+ Matchers.equalTo(Response.Status.OK.getStatusCode()));
+ }
+
+
+ /**
+ * Closes all client resources.
+ */
+ @After
+ public void teardown() {
+ this.eventSource.close();
+ System.out.println("Closed SSE Event source..");
+ this.sseClient.close();
+ System.out.println("Closed JAX-RS client..");
+ }
+
+
+ /**
+ * Registers reaction on events, waits for events and checks their content.
+ *
+ * @throws Exception
+ */
+ @Test(timeout = 5000)
+ @RunAsClient
+ public void testSSE() throws Exception {
+ final Queue asyncExceptions = new ConcurrentLinkedQueue<>();
+ final Queue receivedEvents = new ConcurrentLinkedQueue<>();
+ // jsonb is thread safe!
+ final Jsonb jsonb = JsonbBuilder.create();
+ final Consumer onEvent = (sseEvent) -> {
+ assertThat("event type", sseEvent.getName(), Matchers.isOneOf(EVENT_TYPES));
+ final String data = sseEvent.readData();
+ System.out.println("Data received as string:\n" + data);
+ assertNotNull("data received as string", data);
+ final EventData event = jsonb.fromJson(data, EventData.class);
+ receivedEvents.add(event);
+ assertThat("event.time", event.getTime(), instanceOf(Date.class));
+ assertNotNull("event.id", event.getId());
+ assertThat("event.comment", event.getComment(), Matchers.containsString("event:"));
+ };
+ this.eventSource.register(onEvent, asyncExceptions::add);
+ System.out.println("Server Side Events Client registered in the test thread.");
+ // following line starts acceptation of events.
+ this.eventSource.open();
+ // don't end the test until we have all events or timeout or error comes.
+ // this is not an obvious implementation, we only need to hold the test until all events
+ // are asynchronously processed.
+ while (receivedEvents.size() <= 5 && asyncExceptions.isEmpty()) {
+ Thread.sleep(10L);
+ }
+ assertThat("receiver exceptions", asyncExceptions, Matchers.emptyIterable());
+ }
+}
diff --git a/jpa/dynamic-tx/pom.xml b/jpa/dynamic-tx/pom.xml
new file mode 100644
index 00000000..ba8511cc
--- /dev/null
+++ b/jpa/dynamic-tx/pom.xml
@@ -0,0 +1,14 @@
+
+4.0.0
+
+
+ org.javaee8
+ jpa
+ 1.0-SNAPSHOT
+
+
+ dynamic-tx
+ war
+ Java EE 8 Samples: JPA - Dynamic Transaction
+
+
diff --git a/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/ApplicationInit.java b/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/ApplicationInit.java
new file mode 100644
index 00000000..b6c03e4e
--- /dev/null
+++ b/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/ApplicationInit.java
@@ -0,0 +1,62 @@
+package org.javaee8.jpa.dynamic.tx;
+
+import static java.util.stream.Collectors.toSet;
+
+import java.util.logging.Logger;
+
+import javax.annotation.Priority;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Alternative;
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.InterceptionFactory;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
+import org.javaee8.jpa.dynamic.tx.util.EmployeeBeanWrapper;
+import org.javaee8.jpa.dynamic.tx.util.TransactionLiteral;
+
+
+@Alternative
+@Priority(500)
+@ApplicationScoped
+public class ApplicationInit {
+
+ private static final Logger logger = Logger.getLogger(ApplicationInit.class.getName());
+
+ @Produces @PersistenceContext
+ private EntityManager entityManager;
+
+ @Produces
+ public EmployeeService produce(InterceptionFactory interceptionFactory, BeanManager beanManager) {
+
+ logger.info("Producing EmployeeService");
+
+ EmployeeService employeeBean =
+ createRef(
+ beanManager.resolve(
+ beanManager.getBeans(EmployeeService.class)
+ .stream()
+ .filter(e -> !e.getBeanClass().equals(ApplicationInit.class))
+ .collect(toSet())), beanManager);
+
+ interceptionFactory
+ .configure()
+ .filterMethods(am -> am.getJavaMember().getName().equals("persist"))
+ .forEach(
+ amc -> amc.add(new TransactionLiteral()));
+
+ return interceptionFactory.createInterceptedInstance(
+ new EmployeeBeanWrapper(employeeBean));
+ }
+
+ EmployeeService createRef(Bean> bean, BeanManager beanManager) {
+ return (EmployeeService)
+ beanManager.getReference(
+ bean,
+ EmployeeService.class,
+ beanManager.createCreationalContext(bean));
+ }
+
+}
diff --git a/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/Employee.java b/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/Employee.java
new file mode 100644
index 00000000..d30e3a20
--- /dev/null
+++ b/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/Employee.java
@@ -0,0 +1,31 @@
+package org.javaee8.jpa.dynamic.tx;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+@Entity
+public class Employee {
+
+ @Id
+ @GeneratedValue
+ private int id;
+
+ private String name;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/EmployeeService.java b/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/EmployeeService.java
new file mode 100644
index 00000000..6ccb96d2
--- /dev/null
+++ b/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/EmployeeService.java
@@ -0,0 +1,20 @@
+package org.javaee8.jpa.dynamic.tx;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+
+@ApplicationScoped
+public class EmployeeService {
+
+ @Inject
+ private EntityManager entityManager;
+
+ public void persist(Employee employee) {
+ entityManager.persist(employee);
+ }
+
+ public Employee getById(int id) {
+ return entityManager.find(Employee.class, id);
+ }
+}
diff --git a/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/util/EmployeeBeanWrapper.java b/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/util/EmployeeBeanWrapper.java
new file mode 100644
index 00000000..3cc74c9d
--- /dev/null
+++ b/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/util/EmployeeBeanWrapper.java
@@ -0,0 +1,32 @@
+package org.javaee8.jpa.dynamic.tx.util;
+
+import org.javaee8.jpa.dynamic.tx.Employee;
+import org.javaee8.jpa.dynamic.tx.EmployeeService;
+
+public class EmployeeBeanWrapper extends EmployeeService {
+
+ private EmployeeService employeeBean;
+
+ public EmployeeBeanWrapper() {
+ }
+
+ public EmployeeBeanWrapper(EmployeeService employeeBean) {
+ this.employeeBean = employeeBean;
+ }
+
+ EmployeeService getWrapped() {
+ return employeeBean;
+ }
+
+ @Override
+ public void persist(Employee employee) {
+ getWrapped().persist(employee);
+ }
+
+ @Override
+ public Employee getById(int id) {
+ return getWrapped().getById(id);
+ }
+
+}
+
diff --git a/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/util/TransactionLiteral.java b/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/util/TransactionLiteral.java
new file mode 100644
index 00000000..af814783
--- /dev/null
+++ b/jpa/dynamic-tx/src/main/java/org/javaee8/jpa/dynamic/tx/util/TransactionLiteral.java
@@ -0,0 +1,28 @@
+package org.javaee8.jpa.dynamic.tx.util;
+
+import static javax.transaction.Transactional.TxType.REQUIRED;
+
+import javax.enterprise.util.AnnotationLiteral;
+import javax.transaction.Transactional;
+
+@SuppressWarnings("all")
+public class TransactionLiteral extends AnnotationLiteral implements Transactional {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public TxType value() {
+ return REQUIRED;
+ }
+
+ @Override
+ public Class>[] rollbackOn() {
+ return new Class[0];
+ }
+
+ @Override
+ public Class>[] dontRollbackOn() {
+ return new Class[0];
+ }
+
+}
\ No newline at end of file
diff --git a/jpa/dynamic-tx/src/main/resources/META-INF/persistence.xml b/jpa/dynamic-tx/src/main/resources/META-INF/persistence.xml
new file mode 100644
index 00000000..020731f9
--- /dev/null
+++ b/jpa/dynamic-tx/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/jpa/dynamic-tx/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/jpa/dynamic-tx/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
new file mode 100644
index 00000000..4feda966
--- /dev/null
+++ b/jpa/dynamic-tx/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jpa/dynamic-tx/src/test/java/org/javaee8/jpa/dynamic/tx/DynamicTXTest.java b/jpa/dynamic-tx/src/test/java/org/javaee8/jpa/dynamic/tx/DynamicTXTest.java
new file mode 100644
index 00000000..9b77e54d
--- /dev/null
+++ b/jpa/dynamic-tx/src/test/java/org/javaee8/jpa/dynamic/tx/DynamicTXTest.java
@@ -0,0 +1,58 @@
+package org.javaee8.jpa.dynamic.tx;
+
+import static org.jboss.shrinkwrap.api.ShrinkWrap.create;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.io.File;
+
+import javax.inject.Inject;
+
+import org.hamcrest.Matchers;
+import org.javaee8.jpa.dynamic.tx.ApplicationInit;
+import org.javaee8.jpa.dynamic.tx.Employee;
+import org.javaee8.jpa.dynamic.tx.EmployeeService;
+import org.javaee8.jpa.dynamic.tx.util.EmployeeBeanWrapper;
+import org.javaee8.jpa.dynamic.tx.util.TransactionLiteral;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(Arquillian.class)
+public class DynamicTXTest {
+
+ @Inject
+ private EmployeeService employeeService;
+
+ @Deployment
+ public static WebArchive createDeployment() {
+ return create(WebArchive.class)
+ .addClasses(
+ ApplicationInit.class,
+ Employee.class,
+ EmployeeBeanWrapper.class,
+ EmployeeService.class)
+ .addPackage(
+ TransactionLiteral.class.getPackage())
+ .addAsResource(
+ "META-INF/persistence.xml")
+ .addAsWebInfResource(new File("src/main/webapp/WEB-INF/jboss-deployment-structure.xml"));
+ }
+
+ @Test
+ public void testPersist() throws Exception {
+ Employee employee = new Employee();
+ employee.setName("reza");
+
+ assertThat("employeeService", employeeService, Matchers.notNullValue());
+ employeeService.persist(employee);
+
+ Employee persistedEmployee = employeeService.getById(employee.getId());
+
+ assertEquals("reza", persistedEmployee.getName());
+ }
+
+}
diff --git a/jpa/pom.xml b/jpa/pom.xml
new file mode 100644
index 00000000..523a9fc5
--- /dev/null
+++ b/jpa/pom.xml
@@ -0,0 +1,37 @@
+
+4.0.0
+
+
+ org.javaee8
+ samples-parent
+ 1.0-SNAPSHOT
+
+
+ jpa
+ pom
+ Java EE 8 Samples: JPA
+
+
+ dynamic-tx
+ stream
+
+
+
+
+ org.javaee8
+ test-utils
+ ${project.version}
+
+
+
+
+ thorntail
+
+
+ com.h2database
+ h2
+
+
+
+
+
diff --git a/jpa/stream/pom.xml b/jpa/stream/pom.xml
new file mode 100644
index 00000000..910c3cb0
--- /dev/null
+++ b/jpa/stream/pom.xml
@@ -0,0 +1,13 @@
+
+4.0.0
+
+
+ org.javaee8
+ jpa
+ 1.0-SNAPSHOT
+
+
+ stream
+ war
+ Java EE 8 Samples: JPA - Stream
+
diff --git a/jpa/stream/src/main/java/org/javaee8/jpa/stream/domain/Person.java b/jpa/stream/src/main/java/org/javaee8/jpa/stream/domain/Person.java
new file mode 100644
index 00000000..e6c11668
--- /dev/null
+++ b/jpa/stream/src/main/java/org/javaee8/jpa/stream/domain/Person.java
@@ -0,0 +1,52 @@
+package org.javaee8.jpa.stream.domain;
+
+import java.io.Serializable;
+import javax.persistence.Basic;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+/**
+ *
+ * @author Gaurav Gupta
+ *
+ */
+@Entity
+public class Person implements Serializable {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @Basic
+ private String name;
+
+ @Basic
+ private String address;
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getAddress() {
+ return this.address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+}
\ No newline at end of file
diff --git a/jpa/stream/src/main/java/org/javaee8/jpa/stream/repository/PersonRepository.java b/jpa/stream/src/main/java/org/javaee8/jpa/stream/repository/PersonRepository.java
new file mode 100644
index 00000000..4be6190c
--- /dev/null
+++ b/jpa/stream/src/main/java/org/javaee8/jpa/stream/repository/PersonRepository.java
@@ -0,0 +1,25 @@
+package org.javaee8.jpa.stream.repository;
+
+import java.util.stream.Stream;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.criteria.CriteriaQuery;
+import org.javaee8.jpa.stream.domain.Person;
+
+/**
+ *
+ * @author Gaurav Gupta
+ *
+ */
+public class PersonRepository {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ public Stream findAll() {
+ CriteriaQuery criteriaQuery = entityManager.getCriteriaBuilder().createQuery();
+ criteriaQuery.select(criteriaQuery.from(Person.class));
+ return entityManager.createQuery(criteriaQuery).getResultStream();
+ }
+
+}
diff --git a/jpa/stream/src/test/java/org/javaee8/jpa/stream/controller/PersonControllerTest.java b/jpa/stream/src/test/java/org/javaee8/jpa/stream/controller/PersonControllerTest.java
new file mode 100644
index 00000000..54b0f75f
--- /dev/null
+++ b/jpa/stream/src/test/java/org/javaee8/jpa/stream/controller/PersonControllerTest.java
@@ -0,0 +1,43 @@
+package org.javaee8.jpa.stream.controller;
+
+import java.util.stream.Stream;
+import org.javaee8.jpa.stream.repository.PersonRepository;
+import org.javaee8.jpa.stream.domain.Person;
+import javax.inject.Inject;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.junit.Test;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import static org.junit.Assert.assertEquals;
+import org.junit.runner.RunWith;
+
+/**
+ *
+ * @author Gaurav Gupta
+ *
+ */
+@RunWith(Arquillian.class)
+public class PersonControllerTest {
+
+ @Deployment
+ public static WebArchive createDeployment() {
+ return ShrinkWrap.create(WebArchive.class)
+ .addAsWebInfResource("beans.xml")
+ .addAsResource("test-persistence.xml", "META-INF/persistence.xml")
+ .addAsResource("META-INF/sql/insert.sql")
+ .addClass(Person.class)
+ .addClass(PersonRepository.class);
+ }
+
+ @Inject
+ private PersonRepository personRepository;
+
+ @Test
+ public void testStream() throws Exception {
+ Stream personStream = personRepository.findAll();
+ long personCount = personStream.count();
+ assertEquals(2, personCount);
+ }
+
+}
diff --git a/jpa/stream/src/test/resources/META-INF/sql/insert.sql b/jpa/stream/src/test/resources/META-INF/sql/insert.sql
new file mode 100644
index 00000000..8be18f13
--- /dev/null
+++ b/jpa/stream/src/test/resources/META-INF/sql/insert.sql
@@ -0,0 +1,2 @@
+INSERT INTO person (id, name, address) VALUES (1002, 'Arjan', 'abc')
+INSERT INTO person (id, name, address) VALUES (1001, 'Gaurav', 'xyz')
diff --git a/jpa/stream/src/test/resources/beans.xml b/jpa/stream/src/test/resources/beans.xml
new file mode 100644
index 00000000..2777559c
--- /dev/null
+++ b/jpa/stream/src/test/resources/beans.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/jpa/stream/src/test/resources/test-persistence.xml b/jpa/stream/src/test/resources/test-persistence.xml
new file mode 100644
index 00000000..fc0138df
--- /dev/null
+++ b/jpa/stream/src/test/resources/test-persistence.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jsf/README.md b/jsf/README.md
new file mode 100644
index 00000000..5d31707a
--- /dev/null
+++ b/jsf/README.md
@@ -0,0 +1,11 @@
+# Java EE 8 Samples: JSF 2.3#
+
+The [JSR 372](https://jcp.org/en/jsr/detail?id=372) specifies the next version of JavaServer Faces - JSF 2.3.
+
+## Samples ##
+
+- hello-world shows a typical "hello world" that prints this often used sentence coming from a backing bean
+- extensionless-mapping shows how to configure JSF so that views can be requested without using an extension
+
+
+
diff --git a/jsf/extensionless-mapping/pom.xml b/jsf/extensionless-mapping/pom.xml
new file mode 100644
index 00000000..1f078a5e
--- /dev/null
+++ b/jsf/extensionless-mapping/pom.xml
@@ -0,0 +1,15 @@
+
+4.0.0
+
+
+ org.javaee8
+ jsf
+ 1.0-SNAPSHOT
+
+
+ extensionless-mapping
+ war
+ Java EE 8 Samples: JSF - Extensionless Mapping
+
+
+
diff --git a/jsf/extensionless-mapping/src/main/java/org/javaee8/cdi/dynamic/bean/ApplicationInit.java b/jsf/extensionless-mapping/src/main/java/org/javaee8/cdi/dynamic/bean/ApplicationInit.java
new file mode 100644
index 00000000..a232e89a
--- /dev/null
+++ b/jsf/extensionless-mapping/src/main/java/org/javaee8/cdi/dynamic/bean/ApplicationInit.java
@@ -0,0 +1,20 @@
+package org.javaee8.cdi.dynamic.bean;
+
+import static javax.faces.annotation.FacesConfig.Version.JSF_2_3;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.faces.annotation.FacesConfig;
+
+/**
+ * This class is needed to activate JSF and configure it to be the
+ * right version. Without this being present an explicit mapping
+ * of the FacesServlet in web.xml would be required, but JSF 2.3
+ * would then run in a JSF 2.2 compatibility mode.
+ *
+ * @author Arjan Tijms
+ */
+@FacesConfig(version = JSF_2_3)
+@ApplicationScoped
+public class ApplicationInit {
+
+}
diff --git a/jsf/extensionless-mapping/src/main/java/org/javaee8/cdi/dynamic/bean/MappingInit.java b/jsf/extensionless-mapping/src/main/java/org/javaee8/cdi/dynamic/bean/MappingInit.java
new file mode 100644
index 00000000..50bb1276
--- /dev/null
+++ b/jsf/extensionless-mapping/src/main/java/org/javaee8/cdi/dynamic/bean/MappingInit.java
@@ -0,0 +1,48 @@
+package org.javaee8.cdi.dynamic.bean;
+
+import static javax.faces.application.ViewVisitOption.RETURN_AS_MINIMAL_IMPLICIT_OUTCOME;
+
+import java.util.Map;
+
+import javax.faces.application.Application;
+import javax.faces.context.FacesContext;
+import javax.faces.event.AbortProcessingException;
+import javax.faces.event.SystemEvent;
+import javax.faces.event.SystemEventListener;
+import javax.faces.webapp.FacesServlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRegistration;
+
+/**
+ *
+ * @author Arjan Tijms
+ */
+public class MappingInit implements SystemEventListener {
+
+
+ @Override
+ public void processEvent(SystemEvent event) throws AbortProcessingException {
+
+ FacesContext facesContext = event.getFacesContext();
+ ServletContext sc = (ServletContext) facesContext.getExternalContext().getContext();
+
+ if (Boolean.valueOf((String) sc.getAttribute("mappingsAdded"))) {
+ return;
+ }
+
+ Map servletRegistrations = (Map) sc.getAttribute("mappings");
+
+ if (servletRegistrations == null) {
+ return;
+ }
+
+ MappingServletContextListener.addServletMappings(servletRegistrations, facesContext);
+ }
+
+
+ @Override
+ public boolean isListenerForSource(Object source) {
+ return source instanceof Application;
+ }
+
+}
diff --git a/jsf/extensionless-mapping/src/main/java/org/javaee8/cdi/dynamic/bean/MappingServletContextListener.java b/jsf/extensionless-mapping/src/main/java/org/javaee8/cdi/dynamic/bean/MappingServletContextListener.java
new file mode 100644
index 00000000..d56b8468
--- /dev/null
+++ b/jsf/extensionless-mapping/src/main/java/org/javaee8/cdi/dynamic/bean/MappingServletContextListener.java
@@ -0,0 +1,44 @@
+package org.javaee8.cdi.dynamic.bean;
+
+import static javax.faces.application.ViewVisitOption.RETURN_AS_MINIMAL_IMPLICIT_OUTCOME;
+
+import java.util.Map;
+
+import javax.faces.application.Application;
+import javax.faces.context.FacesContext;
+import javax.faces.webapp.FacesServlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletRegistration;
+import javax.servlet.annotation.WebListener;
+
+@WebListener
+public class MappingServletContextListener implements ServletContextListener {
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce) {
+ ServletContext sc = sce.getServletContext();
+
+ sc.setAttribute("mappings", sce.getServletContext().getServletRegistrations());
+
+ FacesContext facesContext = FacesContext.getCurrentInstance();
+ if (facesContext == null) {
+ //It 's possible that JSF isn't available at this time depending on JSF implementation and Java server container
+ return;
+ }
+
+ addServletMappings(sc.getServletRegistrations(), facesContext);
+
+ //Add a flag to not add the mappings again later in MappingInit
+ sc.setAttribute("mappingsAdded", "true");
+ }
+
+ public static void addServletMappings(Map servletRegistrations, FacesContext facesContext) {
+ servletRegistrations.values().stream().filter(e -> e.getClassName().equals(FacesServlet.class.getName()))
+ .findAny()
+ .ifPresent(reg -> facesContext.getApplication().getViewHandler().getViews(
+ facesContext, "/", RETURN_AS_MINIMAL_IMPLICIT_OUTCOME).forEach(e -> reg.addMapping(e)));
+ }
+
+}
diff --git a/jsf/extensionless-mapping/src/main/webapp/WEB-INF/faces-config.xml b/jsf/extensionless-mapping/src/main/webapp/WEB-INF/faces-config.xml
new file mode 100644
index 00000000..0eae5f11
--- /dev/null
+++ b/jsf/extensionless-mapping/src/main/webapp/WEB-INF/faces-config.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ org.javaee8.cdi.dynamic.bean.MappingInit
+ javax.faces.event.PostConstructApplicationEvent
+
+
+
\ No newline at end of file
diff --git a/jsf/extensionless-mapping/src/main/webapp/bar.xhtml b/jsf/extensionless-mapping/src/main/webapp/bar.xhtml
new file mode 100755
index 00000000..a0f08748
--- /dev/null
+++ b/jsf/extensionless-mapping/src/main/webapp/bar.xhtml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Bar page
+
+
+
+ This is page bar
+
+
+
diff --git a/jsf/extensionless-mapping/src/main/webapp/foo.xhtml b/jsf/extensionless-mapping/src/main/webapp/foo.xhtml
new file mode 100755
index 00000000..395d3e61
--- /dev/null
+++ b/jsf/extensionless-mapping/src/main/webapp/foo.xhtml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Test JSF 2.3 Exact Mapping
+
+
+
+ This is page foo
+
+
+
diff --git a/jsf/extensionless-mapping/src/main/webapp/sub/bar.xhtml b/jsf/extensionless-mapping/src/main/webapp/sub/bar.xhtml
new file mode 100755
index 00000000..cc886e63
--- /dev/null
+++ b/jsf/extensionless-mapping/src/main/webapp/sub/bar.xhtml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Sub-Bar page
+
+
+
+ This is page sub-bar
+
+
+
diff --git a/jsf/extensionless-mapping/src/test/java/org/javaee8/cdi/dynamic/bean/ExtensionlessMappingTest.java b/jsf/extensionless-mapping/src/test/java/org/javaee8/cdi/dynamic/bean/ExtensionlessMappingTest.java
new file mode 100644
index 00000000..c98f4e97
--- /dev/null
+++ b/jsf/extensionless-mapping/src/test/java/org/javaee8/cdi/dynamic/bean/ExtensionlessMappingTest.java
@@ -0,0 +1,99 @@
+package org.javaee8.cdi.dynamic.bean;
+
+import static org.jboss.shrinkwrap.api.ShrinkWrap.create;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+
+/**
+ *
+ * @author Arjan Tijms
+ *
+ */
+@RunWith(Arquillian.class)
+public class ExtensionlessMappingTest {
+
+ @ArquillianResource
+ private URL base;
+
+ private WebClient webClient;
+
+ @Before
+ public void setup() {
+ webClient = new WebClient();
+ }
+
+ @After
+ public void teardown() {
+ webClient.close();
+ }
+
+ @Deployment
+ public static WebArchive deploy() {
+ WebArchive war =
+ create(WebArchive.class)
+ .addClasses(MappingInit.class, ApplicationInit.class, MappingServletContextListener.class)
+ .addAsWebResource(new File("src/main/webapp/foo.xhtml"))
+ .addAsWebResource(new File("src/main/webapp/bar.xhtml"))
+ .addAsWebResource(new File("src/main/webapp/sub/bar.xhtml"), "/sub/bar.xhtml")
+ .addAsWebInfResource("beans.xml")
+ .addAsWebInfResource(new File("src/main/webapp/WEB-INF/faces-config.xml"));
+
+ System.out.println("War to be deployed contains: \n" + war.toString(true));
+
+ return war;
+ }
+
+
+ @Test
+ @RunAsClient
+ public void testExtensionlessMappingFoo() throws IOException {
+
+ HtmlPage page = webClient.getPage(base + "foo");
+ String content = page.asXml();
+
+ System.out.println("\nContent for `"+ base + "foo" + "` :\n" + content + "\n");
+
+ assertTrue(content.contains("This is page foo"));
+ }
+
+ @Test
+ @RunAsClient
+ public void testExtensionlessMappingBar() throws IOException {
+
+ HtmlPage page = webClient.getPage(base + "bar");
+ String content = page.asXml();
+
+ System.out.println("\nContent for `"+ base + "bar" + "` :\n" + content + "\n");
+
+ assertTrue(content.contains("This is page bar"));
+ }
+
+ @Test
+ @RunAsClient
+ public void testExtensionlessMappingSubBar() throws IOException {
+
+ HtmlPage page = webClient.getPage(base + "sub/bar");
+ String content = page.asXml();
+
+ System.out.println("\nContent for `"+ base + "sub/bar" + "` :\n" + content + "\n");
+
+ assertTrue(content.contains("This is page sub-bar"));
+ }
+
+}
diff --git a/jsf/extensionless-mapping/src/test/resources/beans.xml b/jsf/extensionless-mapping/src/test/resources/beans.xml
new file mode 100644
index 00000000..d306e5f6
--- /dev/null
+++ b/jsf/extensionless-mapping/src/test/resources/beans.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/jsf/hello-world/README.md b/jsf/hello-world/README.md
new file mode 100644
index 00000000..d48aecf7
--- /dev/null
+++ b/jsf/hello-world/README.md
@@ -0,0 +1,9 @@
+# Java EE 8 Samples: JSF 2.3 - Hello world#
+
+The [JSR 372](https://jcp.org/en/jsr/detail?id=372) specifies the next version of JavaServer Faces - JSF 2.3.
+
+A very simple example consisting out of a single page, a backing bean and a bean that activates JSF.
+The page prints "Hello world, from JSF!" which comes from the backing bean.
+
+
+
diff --git a/jsf/hello-world/pom.xml b/jsf/hello-world/pom.xml
new file mode 100644
index 00000000..9eef89d4
--- /dev/null
+++ b/jsf/hello-world/pom.xml
@@ -0,0 +1,14 @@
+
+4.0.0
+
+
+ org.javaee8
+ jsf
+ 1.0-SNAPSHOT
+
+
+ hello-world
+ war
+
+ Java EE 8 Samples: JSF - Hello World
+
diff --git a/jsf/hello-world/src/main/java/org/javaee8/jsf/hello/world/ApplicationInit.java b/jsf/hello-world/src/main/java/org/javaee8/jsf/hello/world/ApplicationInit.java
new file mode 100644
index 00000000..c24a81ef
--- /dev/null
+++ b/jsf/hello-world/src/main/java/org/javaee8/jsf/hello/world/ApplicationInit.java
@@ -0,0 +1,19 @@
+/** Copyright Payara Services Limited **/
+package org.javaee8.jsf.hello.world;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.faces.annotation.FacesConfig;
+
+/**
+ * This class is needed to activate JSF and configure it to be the
+ * right version. Without this being present an explicit mapping
+ * of the FacesServlet in web.xml would be required, but JSF 2.3
+ * would then run in a JSF 2.2 compatibility mode.
+ *
+ * @author Arjan Tijms
+ */
+@FacesConfig
+@ApplicationScoped
+public class ApplicationInit {
+
+}
diff --git a/jsf/hello-world/src/main/java/org/javaee8/jsf/hello/world/HelloBacking.java b/jsf/hello-world/src/main/java/org/javaee8/jsf/hello/world/HelloBacking.java
new file mode 100644
index 00000000..bcd3f1c8
--- /dev/null
+++ b/jsf/hello-world/src/main/java/org/javaee8/jsf/hello/world/HelloBacking.java
@@ -0,0 +1,15 @@
+/** Copyright Payara Services Limited **/
+package org.javaee8.jsf.hello.world;
+
+import javax.enterprise.context.RequestScoped;
+import javax.inject.Named;
+
+@Named
+@RequestScoped
+public class HelloBacking {
+
+ public String getHello() {
+ return "Hello world, from JSF!";
+ }
+
+}
diff --git a/jsf/hello-world/src/main/webapp/hello.xhtml b/jsf/hello-world/src/main/webapp/hello.xhtml
new file mode 100755
index 00000000..d352e640
--- /dev/null
+++ b/jsf/hello-world/src/main/webapp/hello.xhtml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ Hello
+
+
+
+
+ JSF says:
+
+
+ #{helloBacking.hello}
+
+
+
+
diff --git a/jsf/hello-world/src/test/java/org/javaee8/jsf/hello/world/JSFHelloWorldTest.java b/jsf/hello-world/src/test/java/org/javaee8/jsf/hello/world/JSFHelloWorldTest.java
new file mode 100644
index 00000000..ce08fefc
--- /dev/null
+++ b/jsf/hello-world/src/test/java/org/javaee8/jsf/hello/world/JSFHelloWorldTest.java
@@ -0,0 +1,72 @@
+/** Copyright Payara Services Limited **/
+package org.javaee8.jsf.hello.world;
+
+import static org.jboss.shrinkwrap.api.ShrinkWrap.create;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+import org.javaee8.jsf.hello.world.ApplicationInit;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+
+/**
+ *
+ * @author Arjan Tijms
+ *
+ */
+@RunWith(Arquillian.class)
+public class JSFHelloWorldTest {
+
+ @ArquillianResource
+ private URL base;
+
+ private WebClient webClient;
+
+ @Before
+ public void setup() {
+ webClient = new WebClient();
+ }
+
+ @After
+ public void teardown() {
+ webClient.close();
+ }
+
+ @Deployment
+ public static WebArchive deploy() {
+ WebArchive war =
+ create(WebArchive.class)
+ .addClasses(ApplicationInit.class, HelloBacking.class)
+ .addAsWebResource(new File("src/main/webapp/hello.xhtml"))
+ ;
+
+ System.out.println("War to be deployed contains: \n" + war.toString(true));
+
+ return war;
+ }
+
+
+ @Test
+ @RunAsClient
+ public void testHelloWorld() throws IOException {
+ HtmlPage page = webClient.getPage(base + "hello.xhtml");
+
+ assertTrue(page.asXml().contains("Hello world, from JSF!"));
+ }
+
+
+
+}
diff --git a/jsf/pom.xml b/jsf/pom.xml
new file mode 100644
index 00000000..5f2daea2
--- /dev/null
+++ b/jsf/pom.xml
@@ -0,0 +1,26 @@
+
+4.0.0
+
+
+ org.javaee8
+ samples-parent
+ 1.0-SNAPSHOT
+
+
+ jsf
+ pom
+ Java EE 8 Samples: JSF
+
+
+ hello-world
+ extensionless-mapping
+
+
+
+
+ org.javaee8
+ test-utils
+ ${project.version}
+
+
+
diff --git a/json-p/merge/pom.xml b/json-p/merge/pom.xml
new file mode 100644
index 00000000..46b22973
--- /dev/null
+++ b/json-p/merge/pom.xml
@@ -0,0 +1,12 @@
+
+4.0.0
+
+
+ org.javaee8
+ json-p
+ 1.0-SNAPSHOT
+
+
+ merge
+ Java EE 8 Samples: JSON-P - Merge
+
\ No newline at end of file
diff --git a/json-p/merge/src/test/java/org/javaee8/jsonp/merge/JsonpMergeTest.java b/json-p/merge/src/test/java/org/javaee8/jsonp/merge/JsonpMergeTest.java
new file mode 100644
index 00000000..ba1660b4
--- /dev/null
+++ b/json-p/merge/src/test/java/org/javaee8/jsonp/merge/JsonpMergeTest.java
@@ -0,0 +1,177 @@
+package org.javaee8.jsonp.merge;
+
+import javax.json.Json;
+import javax.json.JsonMergePatch;
+import javax.json.JsonObject;
+import javax.json.JsonValue;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Class that tests and demonstrates the JSON-P 1.1 Merge Operations.
+ * @author Andrew Pielage
+ */
+@RunWith(Arquillian.class)
+public class JsonpMergeTest {
+
+ // Create a JsonObject with some values to be used in each test
+ private static final JsonObject json = Json.createObjectBuilder()
+ .add("Wibbly", "Wobbly")
+ .add("Replaced", false)
+ .add("Lexicon", Json.createArrayBuilder()
+ .add("Wibbles")
+ .add("Wobbles")
+ .build())
+ .add("Nested", Json.createObjectBuilder()
+ .add("Birdie", "Wordie")
+ .add("Bestiary", Json.createArrayBuilder()
+ .add("Drowner")
+ .add("Werewolf")
+ .add("Chimera")
+ .build())
+ .build())
+ .build();
+
+ @Deployment
+ public static JavaArchive createDeployment() {
+ // Create a JavaArchive to deploy
+ JavaArchive jar = ShrinkWrap.create(JavaArchive.class);
+
+ // Print out directory contents
+ System.out.println(jar.toString(true));
+
+ // Return Arquillian Test Archive for application server
+ return jar;
+ }
+
+ /**
+ * Test that the JSON Merge operation replaces values as intended.
+ */
+ @Test
+ public void replaceTest() {
+ // Create a JSON object that we'll merge into the class variable, replacing object members and array values
+ JsonObject jsonToMerge = Json.createObjectBuilder()
+ .add("Wibbly", "Bibbly")
+ .add("Replaced", "Yes")
+ .add("Lexicon", Json.createArrayBuilder()
+ .add("Wibbles")
+ .add("Bibbles")
+ .build())
+ .add("Nested", Json.createObjectBuilder()
+ .add("Bestiary", Json.createArrayBuilder()
+ .add("Slyzard")
+ .add("Dragon")
+ .add("Ekimmara")
+ .build())
+ .build())
+ .build();
+
+ // Create a merge patch and apply it
+ JsonMergePatch mergePatch = Json.createMergePatch(jsonToMerge);
+ JsonValue mergedJson = mergePatch.apply(json);
+
+ // Print out to more easily see what we've done
+ System.out.println("JsonpMergeTest.replaceTest: Before Merge: " + json);
+ System.out.println("JsonpMergeTest.replaceTest: JSON to Merge: " + jsonToMerge);
+ System.out.println("JsonpMergeTest.replaceTest: After Merge: " + mergedJson);
+
+ // Test that everything is as it should be
+ JsonObject mergedJsonObject = mergedJson.asJsonObject();
+ assertTrue("Merged JSON didn't merge correctly!", mergedJsonObject.getString("Wibbly").equals("Bibbly"));
+ assertTrue("Merged JSON didn't merge correctly!", mergedJsonObject.getString("Replaced").equals("Yes"));
+ assertTrue("JSON Array didn't merge correctly!",
+ mergedJsonObject.getJsonArray("Lexicon").getString(0).equals("Wibbles")
+ && mergedJsonObject.getJsonArray("Lexicon").getString(1).equals("Bibbles"));
+ assertTrue("Nested JSON didn't merge correctly!",
+ mergedJsonObject.getJsonObject("Nested").getString("Birdie").equals("Wordie"));
+ assertTrue("Nested JSON Array didn't merge correctly!",
+ mergedJsonObject.getJsonObject("Nested").getJsonArray("Bestiary").getString(0).equals("Slyzard")
+ && mergedJsonObject.getJsonObject("Nested").getJsonArray("Bestiary").getString(1).equals("Dragon")
+ && mergedJsonObject.getJsonObject("Nested").getJsonArray("Bestiary").getString(2).equals("Ekimmara"));
+ }
+
+ /**
+ * Test that the JSON Merge operation adds values as intended.
+ */
+ @Test
+ public void addTest() {
+ // Create a JSON object that we'll merge into the class variable, adding object members and array values
+ JsonObject jsonToMerge = Json.createObjectBuilder()
+ .add("Bibbly", "Bobbly")
+ .add("Lexicon", Json.createArrayBuilder()
+ .add("Wibbles")
+ .add("Wobbles")
+ .add("Bibbles")
+ .add("Bobbles")
+ .build())
+ .build();
+
+ // Create a merge patch and apply it
+ JsonMergePatch mergePatch = Json.createMergePatch(jsonToMerge);
+ JsonValue mergedJson = mergePatch.apply(json);
+
+ // Print out to more easily see what we've done
+ System.out.println("JsonpMergeTest.addTest: Before Merge: " + json);
+ System.out.println("JsonpMergeTest.addTest: JSON to Merge: " + jsonToMerge);
+ System.out.println("JsonpMergeTest.addTest: After Merge: " + mergedJson);
+
+ // Test that everything is as it should be
+ JsonObject mergedJsonObject = mergedJson.asJsonObject();
+ assertTrue("Merged JSON didn't merge correctly!", mergedJsonObject.getString("Wibbly").equals("Wobbly"));
+ assertTrue("Merged JSON didn't merge correctly!", mergedJsonObject.getString("Bibbly").equals("Bobbly"));
+ assertTrue("Merged JSON didn't merge correctly!", !mergedJsonObject.getBoolean("Replaced"));
+ assertTrue("JSON Array didn't merge correctly!",
+ mergedJsonObject.getJsonArray("Lexicon").getString(0).equals("Wibbles")
+ && mergedJsonObject.getJsonArray("Lexicon").getString(1).equals("Wobbles")
+ && mergedJsonObject.getJsonArray("Lexicon").getString(2).equals("Bibbles")
+ && mergedJsonObject.getJsonArray("Lexicon").getString(3).equals("Bobbles"));
+ assertTrue("Nested JSON didn't merge correctly!",
+ mergedJsonObject.getJsonObject("Nested").getString("Birdie").equals("Wordie"));
+ assertTrue("Nested JSON Array didn't merge correctly!",
+ mergedJsonObject.getJsonObject("Nested").getJsonArray("Bestiary").getString(0).equals("Drowner")
+ && mergedJsonObject.getJsonObject("Nested").getJsonArray("Bestiary").getString(1).equals("Werewolf")
+ && mergedJsonObject.getJsonObject("Nested").getJsonArray("Bestiary").getString(2).equals("Chimera"));
+ }
+
+ /**
+ * Test that the JSON Merge operation removes values as intended.
+ */
+ @Test
+ public void removeTest() {
+ // Create a JSON object that we'll merge into the class variable, removing object members and array values
+ JsonObject jsonToMerge = Json.createObjectBuilder()
+ .addNull("Wibbly")
+ .add("Lexicon", Json.createArrayBuilder()
+ .add("Wibbles")
+ .build())
+ .add("Nested", Json.createObjectBuilder()
+ .addNull("Bestiary")
+ .build())
+ .build();
+
+ // Create a merge patch and apply it
+ JsonMergePatch mergePatch = Json.createMergePatch(jsonToMerge);
+ JsonValue mergedJson = mergePatch.apply(json);
+
+ // Print out to more easily see what we've done
+ System.out.println("JsonpMergeTest.removeTest: Before Merge: " + json);
+ System.out.println("JsonpMergeTest.removeTest: JSON to Merge: " + jsonToMerge);
+ System.out.println("JsonpMergeTest.removeTest: After Merge: " + mergedJson);
+
+ // Test that everything is as it should be
+ JsonObject mergedJsonObject = mergedJson.asJsonObject();
+ assertTrue("Merged JSON didn't merge correctly!", !mergedJsonObject.containsKey("Wibbly"));
+ assertTrue("Merged JSON didn't merge correctly!", !mergedJsonObject.getBoolean("Replaced"));
+ assertTrue("JSON Array didn't merge correctly!",
+ mergedJsonObject.getJsonArray("Lexicon").getString(0).equals("Wibbles"));
+ assertTrue("Nested JSON didn't merge correctly!",
+ mergedJsonObject.getJsonObject("Nested").getString("Birdie").equals("Wordie"));
+ assertTrue("Nested JSON Array didn't merge correctly!",
+ !mergedJsonObject.getJsonObject("Nested").containsKey("Bestiary"));
+ }
+}
diff --git a/json-p/patch/pom.xml b/json-p/patch/pom.xml
new file mode 100644
index 00000000..47731a0d
--- /dev/null
+++ b/json-p/patch/pom.xml
@@ -0,0 +1,12 @@
+
+4.0.0
+
+
+ org.javaee8
+ json-p
+ 1.0-SNAPSHOT
+
+
+ patch
+ Java EE 8 Samples: JSON-P - Patch
+
\ No newline at end of file
diff --git a/json-p/patch/src/test/java/org/javaee8/jsonp/patch/JsonpPatchTest.java b/json-p/patch/src/test/java/org/javaee8/jsonp/patch/JsonpPatchTest.java
new file mode 100644
index 00000000..3acf085d
--- /dev/null
+++ b/json-p/patch/src/test/java/org/javaee8/jsonp/patch/JsonpPatchTest.java
@@ -0,0 +1,219 @@
+package org.javaee8.jsonp.patch;
+
+import javax.json.Json;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonPatch;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.Assert;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Class that tests and demonstrates the JSON-P 1.1 Patch Operations.
+ * @author Andrew Pielage
+ */
+@RunWith(Arquillian.class)
+public class JsonpPatchTest {
+
+ // Create a JsonObject with some values to be used in each test
+ private static final JsonObject json = Json.createObjectBuilder()
+ .add("Wibbly", "Wobbly")
+ .add("Replaced", false)
+ .add("Lexicon", Json.createArrayBuilder()
+ .add("Wibbles")
+ .add("Wobbles")
+ .build())
+ .add("Nested", Json.createObjectBuilder()
+ .add("Birdie", "Wordie")
+ .add("Bestiary", Json.createArrayBuilder()
+ .add("Drowner")
+ .add("Werewolf")
+ .add("Chimera")
+ .build())
+ .build())
+ .build();
+
+ @Deployment
+ public static JavaArchive createDeployment() {
+ // Create a JavaArchive to deploy
+ JavaArchive jar = ShrinkWrap.create(JavaArchive.class);
+
+ // Print out directory contents
+ System.out.println(jar.toString(true));
+
+ // Return Arquillian Test Archive for application server
+ return jar;
+ }
+
+ /**
+ * Test that the JSON Patch add operation works as intended.
+ */
+ @Test
+ public void addTest() {
+ // Create a patch that adds some object members, replaces the value of an already existing object member, and
+ // adds some extra elements to an array
+ JsonPatch patch = Json.createPatchBuilder()
+ .add("/Timey", "Wimey")
+ .add("/Replaced", true)
+ .add("/FunnyReference", true)
+ .add("/FunScore", 100)
+ .add("/Lexicon/2", "Toddles")
+ .add("/Lexicon/2", "Tiddles")
+ .build();
+
+ // Apply the patch
+ JsonObject patchedJson = patch.apply(json);
+
+ // Print out to more easily see what we've done
+ System.out.println("JsonpPatchTest.addTest: Before Patch: " + json);
+ System.out.println("JsonpPatchTest.addTest: After Patch: " + patchedJson);
+
+ // Test that everything is as it should be
+ assertTrue("Patched JSON doesn't match!", patchedJson.getString("Wibbly").equals("Wobbly"));
+ assertTrue("Patched JSON doesn't match!", patchedJson.getString("Timey").equals("Wimey"));
+ assertTrue("Patched JSON doesn't match!", patchedJson.getBoolean("FunnyReference"));
+ assertTrue("Patched JSON doesn't match!", patchedJson.getInt("FunScore") == 100);
+ assertTrue("Patched JSON doesn't match!", patchedJson.getBoolean("Replaced"));
+ assertTrue("Patched JSON doesn't match!", patchedJson.getJsonArray("Lexicon").getString(0)
+ .equals("Wibbles"));
+ assertTrue("Patched JSON doesn't match!", patchedJson.getJsonArray("Lexicon").getString(1)
+ .equals("Wobbles"));
+ assertTrue("Patched JSON doesn't match!", patchedJson.getJsonArray("Lexicon").getString(2)
+ .equals("Tiddles"));
+ assertTrue("Patched JSON doesn't match!", patchedJson.getJsonArray("Lexicon").getString(3)
+ .equals("Toddles"));
+ }
+
+ /**
+ * Test that the JSON Patch remove operation works as intended.
+ */
+ @Test
+ public void removeTest() {
+ // Create a patch that removes an object member and an array element
+ JsonPatch patch = Json.createPatchBuilder()
+ .remove("/Replaced")
+ .remove("/Lexicon/1")
+ .build();
+
+ // Apply the patch
+ JsonObject patchedJson = patch.apply(json);
+
+ // Print out to more easily see what we've done
+ System.out.println("JsonpPatchTest.removeTest: Before Patch: " + json);
+ System.out.println("JsonpPatchTest.removeTest: After Patch: " + patchedJson);
+
+ assertTrue("Patched JSON still contains object member!", !patchedJson.containsKey("Replaced"));
+ assertTrue("Patched JSON still contains object member!", ((patchedJson.getJsonArray("Lexicon").size() == 1)
+ && (patchedJson.getJsonArray("Lexicon").getString(0).equals("Wibbles"))));
+ }
+
+ /**
+ * Test that the JSON Patch replace operation works as intended.
+ */
+ @Test
+ public void replaceTest() {
+ // Create a patch that replaces an object member and an array element
+ JsonPatch patch = Json.createPatchBuilder()
+ .replace("/Replaced", true)
+ .replace("/Lexicon/0", "Tiddles")
+ .replace("/Lexicon/1", "Toddles")
+ .build();
+
+ // Apply the patch
+ JsonObject patchedJson = patch.apply(json);
+
+ // Print out to more easily see what we've done
+ System.out.println("JsonpPatchTest.replaceTest: Before Patch: " + json);
+ System.out.println("JsonpPatchTest.replaceTest: After Patch: " + patchedJson);
+
+ assertTrue("Patched JSON still contains original value!", patchedJson.getBoolean("Replaced"));
+ assertTrue("Patched JSON still contains original values!",
+ ((patchedJson.getJsonArray("Lexicon").getString(0).equals("Tiddles"))
+ && (patchedJson.getJsonArray("Lexicon").getString(1).equals("Toddles"))));
+ }
+
+ /**
+ * Test that the JSON Patch move operation works as intended.
+ */
+ @Test
+ public void moveTest() {
+ // Create a patch that moves an object member, moves an array element, and reorders an array
+ JsonPatch patch = Json.createPatchBuilder()
+ .move("/Nested/Tibbly", "/Wibbly")
+ .move("/Nested/Bestiary/2", "/Lexicon/1")
+ .move("/Nested/Bestiary/3", "/Nested/Bestiary/0")
+ .build();
+
+ // Apply the patch
+ JsonObject patchedJson = patch.apply(json);
+
+ // Print out to more easily see what we've done
+ System.out.println("JsonpPatchTest.moveTest: Before Patch: " + json);
+ System.out.println("JsonpPatchTest.moveTest: After Patch: " + patchedJson);
+
+ assertTrue("Patched JSON hasn't moved value!", (!patchedJson.containsKey("Wibbly")
+ && patchedJson.getJsonObject("Nested").getString("Tibbly").equals("Wobbly")));
+ assertTrue("Patched JSON hasn't moved value!", ((patchedJson.getJsonArray("Lexicon").size() == 1))
+ && (patchedJson.getJsonObject("Nested").getJsonArray("Bestiary").getString(2).equals("Chimera"))
+ && (patchedJson.getJsonObject("Nested").getJsonArray("Bestiary").getString(0).equals("Werewolf")));
+ }
+
+ /**
+ * Test that the JSON Patch copy operation works as intended.
+ */
+ @Test
+ public void copyTest() {
+ // Create a patch that copies an object member and an array element
+ JsonPatch patch = Json.createPatchBuilder()
+ .copy("/Nested/Tobbly", "/Wibbly")
+ .copy("/Nested/Bestiary/2", "/Lexicon/0")
+ .build();
+
+ // Apply the patch
+ JsonObject patchedJson = patch.apply(json);
+
+ // Print out to more easily see what we've done
+ System.out.println("JsonpPatchTest.copyTest: Before Patch: " + json);
+ System.out.println("JsonpPatchTest.copyTest: After Patch: " + patchedJson);
+
+ assertTrue("Patched JSON hasn't moved value!", (patchedJson.containsKey("Wibbly")
+ && patchedJson.getJsonObject("Nested").getString("Tobbly").equals("Wobbly")));
+ assertTrue("Patched JSON hasn't moved value!", ((patchedJson.getJsonArray("Lexicon").size() == 2))
+ && (patchedJson.getJsonObject("Nested").getJsonArray("Bestiary").getString(2).equals("Wibbles")));
+ }
+
+ /**
+ * Test that the JSON Patch test operation works as intended.
+ */
+ @Test
+ public void testTest() {
+ // Create a patch that should test positive
+ JsonPatch patch = Json.createPatchBuilder()
+ .test("/Wibbly", "Wobbly")
+ .test("/Lexicon/0", "Wibbles")
+ .build();
+
+ // Apply the patch
+ JsonObject patchedJson = patch.apply(json);
+
+ // Create a patch that should fail
+ patch = Json.createPatchBuilder()
+ .test("/Wibbly", "Tobbly")
+ .build();
+
+ try {
+ patchedJson = patch.apply(json);
+ } catch (JsonException ex) {
+ return;
+ }
+
+ Assert.fail("Should have caught a JsonException and exited!");
+ }
+
+ // TODO: Ignore unrecognised elements; Adding to a non-existent target; Adding a nested member object; Error Handling
+}
diff --git a/json-p/pointer/pom.xml b/json-p/pointer/pom.xml
new file mode 100644
index 00000000..3c195d55
--- /dev/null
+++ b/json-p/pointer/pom.xml
@@ -0,0 +1,12 @@
+
+4.0.0
+
+
+ org.javaee8
+ json-p
+ 1.0-SNAPSHOT
+
+
+ pointer
+ Java EE 8 Samples: JSON-P - Pointer
+
\ No newline at end of file
diff --git a/json-p/pointer/src/test/java/org/javaee8/jsonp/pointer/JsonpPointerTest.java b/json-p/pointer/src/test/java/org/javaee8/jsonp/pointer/JsonpPointerTest.java
new file mode 100644
index 00000000..20377bf9
--- /dev/null
+++ b/json-p/pointer/src/test/java/org/javaee8/jsonp/pointer/JsonpPointerTest.java
@@ -0,0 +1,169 @@
+package org.javaee8.jsonp.pointer;
+
+import java.math.BigDecimal;
+import javax.json.Json;
+import javax.json.JsonMergePatch;
+import javax.json.JsonObject;
+import javax.json.JsonPointer;
+import javax.json.JsonValue;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Class that tests and demonstrates the JSON-P 1.1 Pointer Operations.
+ * @author Andrew Pielage
+ */
+@RunWith(Arquillian.class)
+public class JsonpPointerTest {
+
+ // Create a JsonObject with some values to be used in each test
+ private static final JsonObject json = Json.createObjectBuilder()
+ .add("Wibbly", "Wobbly")
+ .add("Replaced", false)
+ .add("Lexicon", Json.createArrayBuilder()
+ .add("Wibbles")
+ .add("Wobbles")
+ .build())
+ .add("Nested", Json.createObjectBuilder()
+ .add("Birdie", "Wordie")
+ .add("Bestiary", Json.createArrayBuilder()
+ .add("Drowner")
+ .add("Werewolf")
+ .add("Chimera")
+ .build())
+ .build())
+ .build();
+
+ @Deployment
+ public static JavaArchive createDeployment() {
+ // Create a JavaArchive to deploy
+ JavaArchive jar = ShrinkWrap.create(JavaArchive.class);
+
+ // Print out directory contents
+ System.out.println(jar.toString(true));
+
+ // Return Arquillian Test Archive for application server
+ return jar;
+ }
+
+ /**
+ * Test that the JSON Pointers resolve to the correct values as.
+ */
+ @Test
+ public void resolveTest() {
+ // Create pointers
+ JsonPointer objectPointer = Json.createPointer("");
+ JsonPointer objectMemberPointer = Json.createPointer("/Wibbly");
+ JsonPointer arrayPointer = Json.createPointer("/Lexicon");
+ JsonPointer arrayElementPointer = Json.createPointer("/Lexicon/0");
+ JsonPointer nestedObjectPointer = Json.createPointer("/Nested");
+ JsonPointer nestedObjectMemberPointer = Json.createPointer("/Nested/Birdie");
+ JsonPointer nestedArrayPointer = Json.createPointer("/Nested/Bestiary");
+ JsonPointer nestedArrayElementPointer = Json.createPointer("/Nested/Bestiary/1");
+
+ // Check pointers return the correct values
+ assertTrue("objectPointer doesn't resolve correctly!", objectPointer.getValue(json).equals(json));
+ assertTrue("objectMemberPointer doesn't resolve correctly!",
+ objectMemberPointer.getValue(json).toString().equals("\"Wobbly\""));
+ assertTrue("arrayPointer doesn't resolve correctly!",
+ arrayPointer.getValue(json).equals(json.getJsonArray("Lexicon")));
+ assertTrue("arrayElementPointer doesn't resolve correctly!",
+ arrayElementPointer.getValue(json).toString().equals("\"Wibbles\""));
+ assertTrue("nestedObjectPointer doesn't resolve correctly!",
+ nestedObjectPointer.getValue(json).equals(json.getJsonObject("Nested")));
+ assertTrue("nestedObjectMemberPointer doesn't resolve correctly!",
+ nestedObjectMemberPointer.getValue(json).toString().equals("\"Wordie\""));
+ assertTrue("nestedArrayPointer doesn't resolve correctly!",
+ nestedArrayPointer.getValue(json).equals(json.getJsonObject("Nested").getJsonArray("Bestiary")));
+ assertTrue("nestedArrayElementPointer doesn't resolve correctly!",
+ nestedArrayElementPointer.getValue(json).toString().equals("\"Werewolf\""));
+
+ // Check alternative notation
+ assertTrue("objectMemberPointer doesn't resolve correctly!", json.getValue("/Wibbly").toString().equals("\"Wobbly\""));
+ assertTrue("objectMemberPointer doesn't resolve correctly!",
+ json.getValue("/Nested/Bestiary/2").toString().equals("\"Chimera\""));
+ }
+
+ /**
+ * Test that the JSON Pointer add operation works as expected.
+ */
+ @Test
+ public void addTest() {
+ // Create pointers
+ JsonPointer objectMemberPointer = Json.createPointer("/Giggly");
+ JsonPointer arrayElementPointer = Json.createPointer("/Lexicon/0");
+ JsonPointer nestedObjectMemberPointer = Json.createPointer("/Nested/Purdie");
+ JsonPointer nestedArrayElementPointer = Json.createPointer("/Nested/Bestiary/1");
+
+ // Perform an add operation on each pointer
+ JsonObject modifiedJson = objectMemberPointer.add(json, Json.createValue("Goggly"));
+ modifiedJson = arrayElementPointer.add(modifiedJson, Json.createValue("Giggles"));
+ modifiedJson = nestedObjectMemberPointer.add(modifiedJson, Json.createValue("Furdie"));
+ modifiedJson = nestedArrayElementPointer.add(modifiedJson, Json.createValue("Wraith"));
+
+ // Check that they've been added
+ assertTrue("Object member not added!", modifiedJson.containsKey("Giggly")
+ && modifiedJson.getString("Giggly").equals("Goggly"));
+ assertTrue("Array element not added!", modifiedJson.getJsonArray("Lexicon").size() == 3
+ && modifiedJson.getJsonArray("Lexicon").getString(0).equals("Giggles"));
+ assertTrue("Nested object member not added!", modifiedJson.getJsonObject("Nested").containsKey("Purdie")
+ && modifiedJson.getJsonObject("Nested").getString("Purdie").equals("Furdie"));
+ assertTrue("Nested array element not added!",
+ modifiedJson.getJsonObject("Nested").getJsonArray("Bestiary").size() == 4
+ && modifiedJson.getJsonObject("Nested").getJsonArray("Bestiary").getString(1).equals("Wraith"));
+ }
+
+ /**
+ * Test that the JSON Pointer remove operation works as expected.
+ */
+ @Test
+ public void removeTest() {
+ // Create pointers
+ JsonPointer objectMemberPointer = Json.createPointer("/Wibbly");
+ JsonPointer arrayElementPointer = Json.createPointer("/Lexicon/0");
+ JsonPointer nestedObjectMemberPointer = Json.createPointer("/Nested/Birdie");
+ JsonPointer nestedArrayPointer = Json.createPointer("/Nested/Bestiary");
+
+ // Perform a remove operation using each pointer
+ JsonObject modifiedJson = objectMemberPointer.remove(json);
+ modifiedJson = arrayElementPointer.remove(modifiedJson);
+ modifiedJson = nestedObjectMemberPointer.remove(modifiedJson);
+ modifiedJson = nestedArrayPointer.remove(modifiedJson);
+
+ // Check that they've been removed
+ assertTrue("Object member not removed!", !modifiedJson.containsKey("Wibbly"));
+ assertTrue("Array element not removed!", modifiedJson.getJsonArray("Lexicon").size() == 1
+ && !modifiedJson.getJsonArray("Lexicon").getString(0).equals("Drowner"));
+ assertTrue("Nested object member not removed!", !modifiedJson.getJsonObject("Nested").containsKey("Birdie"));
+ assertTrue("Nested array not removed!", !modifiedJson.getJsonObject("Nested").containsKey("Bestiary"));
+ }
+
+ /**
+ * Test that the JSON Pointer replace operation works as expected.
+ */
+ @Test
+ public void replaceTest() {
+ // Create pointers
+ JsonPointer objectMemberPointer = Json.createPointer("/Wibbly");
+ JsonPointer arrayElementPointer = Json.createPointer("/Lexicon/0");
+ JsonPointer nestedObjectMemberPointer = Json.createPointer("/Nested/Birdie");
+
+ // Perform a replace operation using each pointer
+ JsonObject modifiedJson = objectMemberPointer.replace(json, Json.createValue("Bobbly"));
+ modifiedJson = arrayElementPointer.replace(modifiedJson, Json.createValue("Tiddles"));
+ modifiedJson = nestedObjectMemberPointer.replace(modifiedJson, Json.createValue("Bubbly"));
+
+ // Check that they've been replaced
+ assertTrue("Object member not added!", modifiedJson.containsKey("Wibbly")
+ && modifiedJson.getString("Wibbly").equals("Bobbly"));
+ assertTrue("Array element not added!", modifiedJson.getJsonArray("Lexicon").size() == 2
+ && modifiedJson.getJsonArray("Lexicon").getString(0).equals("Tiddles"));
+ assertTrue("Nested object member not added!", modifiedJson.getJsonObject("Nested").containsKey("Birdie")
+ && modifiedJson.getJsonObject("Nested").getString("Birdie").equals("Bubbly"));
+ }
+}
diff --git a/json-p/pom.xml b/json-p/pom.xml
new file mode 100644
index 00000000..27cc72e3
--- /dev/null
+++ b/json-p/pom.xml
@@ -0,0 +1,28 @@
+
+
+ 4.0.0
+
+ org.javaee8
+ samples-parent
+ 1.0-SNAPSHOT
+
+
+ json-p
+ pom
+ Java EE 8 Samples: JSON-P
+
+
+ patch
+ pointer
+ merge
+
+
+
+
+ org.javaee8
+ test-utils
+ ${project.version}
+
+
+
+
\ No newline at end of file
diff --git a/jsonb/mapping/pom.xml b/jsonb/mapping/pom.xml
new file mode 100644
index 00000000..7904debd
--- /dev/null
+++ b/jsonb/mapping/pom.xml
@@ -0,0 +1,14 @@
+
+4.0.0
+
+
+ org.javaee8
+ jsonb
+ 1.0-SNAPSHOT
+
+
+ mapping
+ war
+ Java EE 8 Samples: JSON-B - Mapping
+
+
diff --git a/jsonb/mapping/src/main/java/org/javaee8/jsonb/mapping/controller/ApplicationConfig.java b/jsonb/mapping/src/main/java/org/javaee8/jsonb/mapping/controller/ApplicationConfig.java
new file mode 100644
index 00000000..b285fee9
--- /dev/null
+++ b/jsonb/mapping/src/main/java/org/javaee8/jsonb/mapping/controller/ApplicationConfig.java
@@ -0,0 +1,26 @@
+package org.javaee8.jsonb.mapping.controller;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.core.Application;
+
+/**
+ *
+ * @author Gaurav Gupta
+ *
+ */
+@javax.ws.rs.ApplicationPath("resources")
+public class ApplicationConfig extends Application {
+
+ @Override
+ public Set> getClasses() {
+ Set> resources = new HashSet<>();
+ addRestResourceClasses(resources);
+ return resources;
+ }
+
+ private void addRestResourceClasses(Set> resources) {
+ resources.add(org.javaee8.jsonb.mapping.controller.PersonController.class);
+ }
+
+}
diff --git a/jsonb/mapping/src/main/java/org/javaee8/jsonb/mapping/controller/PersonController.java b/jsonb/mapping/src/main/java/org/javaee8/jsonb/mapping/controller/PersonController.java
new file mode 100644
index 00000000..c155dda3
--- /dev/null
+++ b/jsonb/mapping/src/main/java/org/javaee8/jsonb/mapping/controller/PersonController.java
@@ -0,0 +1,40 @@
+package org.javaee8.jsonb.mapping.controller;
+
+import org.javaee8.jsonb.mapping.domain.Person;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+/**
+ *
+ * @author Gaurav Gupta
+ */
+@Path("/api/person")
+public class PersonController {
+
+ /**
+ * GET : get all the people.
+ *
+ * @return the Response with status 200 (OK) and the list of people in body
+ *
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public List getAllPeople() {
+ Person person1 = new Person();
+ person1.setName("Ondrej");
+ person1.setAddress("Prague");
+ person1.setPin("Mihalyi");
+
+ Person person2 = new Person();
+ person2.setName("Mert");
+ person2.setAddress("Turkey");
+ person2.setPin("Caliskan");
+
+ return Arrays.asList(person1, person2);
+ }
+
+}
diff --git a/jsonb/mapping/src/main/java/org/javaee8/jsonb/mapping/domain/Person.java b/jsonb/mapping/src/main/java/org/javaee8/jsonb/mapping/domain/Person.java
new file mode 100644
index 00000000..430e27ea
--- /dev/null
+++ b/jsonb/mapping/src/main/java/org/javaee8/jsonb/mapping/domain/Person.java
@@ -0,0 +1,54 @@
+package org.javaee8.jsonb.mapping.domain;
+
+import java.io.Serializable;
+import javax.json.bind.annotation.JsonbProperty;
+import javax.json.bind.annotation.JsonbTransient;
+
+/**
+ *
+ * @author Gaurav Gupta
+ *
+ */
+public class Person implements Serializable {
+
+ /**
+ * JsonbProperty is used to change name of one particular property.
+ * Property 'name' will be serialized to 'pname' property
+ */
+ @JsonbProperty("pname")
+ private String name;
+
+ private String address;
+
+ /**
+ * Property 'pin' will be ignored by JSON Binding engine
+ */
+ @JsonbTransient
+ private String pin;
+
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getAddress() {
+ return this.address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public String getPin() {
+ return pin;
+ }
+
+ public void setPin(String pin) {
+ this.pin = pin;
+ }
+
+}
\ No newline at end of file
diff --git a/jsonb/mapping/src/test/java/org/javaee8/jsonb/mapping/controller/PersonControllerTest.java b/jsonb/mapping/src/test/java/org/javaee8/jsonb/mapping/controller/PersonControllerTest.java
new file mode 100644
index 00000000..8c6df0dc
--- /dev/null
+++ b/jsonb/mapping/src/test/java/org/javaee8/jsonb/mapping/controller/PersonControllerTest.java
@@ -0,0 +1,63 @@
+package org.javaee8.jsonb.mapping.controller;
+
+import org.javaee8.jsonb.mapping.controller.ApplicationConfig;
+import org.javaee8.jsonb.mapping.controller.PersonController;
+import java.net.URI;
+import java.net.URL;
+import java.util.List;
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import org.javaee8.jsonb.mapping.domain.Person;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.junit.Test;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import static org.junit.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+/**
+ *
+ * @author Gaurav Gupta
+ *
+ */
+@RunWith(Arquillian.class)
+public class PersonControllerTest {
+
+ private static final String RESOURCE_PATH = "api/person";
+
+ @ArquillianResource
+ private URL base;
+
+ private static WebTarget target;
+
+ @Deployment
+ public static WebArchive createDeployment() {
+ return ShrinkWrap.create(WebArchive.class)
+ .addClass(Person.class)
+ .addClass(PersonController.class)
+ .addClass(ApplicationConfig.class);
+ }
+
+ @Before
+ public void setUpClass() throws Exception {
+ Client client = ClientBuilder.newClient();
+ target = client.target(URI.create(new URL(base, "resources/").toExternalForm()));
+ }
+
+ @Test
+ public void testJSONB() throws Exception {
+ Jsonb jsonb = JsonbBuilder.create();
+ // Get all the people
+ Response response = target.path(RESOURCE_PATH).request().get();
+ String val = jsonb.toJson(response.readEntity(List.class));
+ assertEquals("[{\"address\":\"Prague\",\"pname\":\"Ondrej\"},{\"address\":\"Turkey\",\"pname\":\"Mert\"}]", val);
+ }
+
+}
diff --git a/jsonb/pom.xml b/jsonb/pom.xml
new file mode 100644
index 00000000..6a00f103
--- /dev/null
+++ b/jsonb/pom.xml
@@ -0,0 +1,25 @@
+
+4.0.0
+
+
+ org.javaee8
+ samples-parent
+ 1.0-SNAPSHOT
+
+
+ jsonb
+ pom
+ Java EE 8 Samples: JSON-B
+
+
+ mapping
+
+
+
+
+ org.javaee8
+ test-utils
+ ${project.version}
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 541a37c9..1c2aa1f5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,28 +1,115 @@
+
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0org.javaee8
- javaee8-samples
+ samples-parent1.0-SNAPSHOTpom
- Java EE 8 Samples
+
+ Java EE 8 Samples: root1.83.0.0UTF-8
-
- 1.3.1
+
false
+ false
+
+ ${skipTests}
+ ${skipTests}
+ ${skipTests}
+ ${skipTests}
+
+
+ 5.183
+ 5.183
+ 5.0
+ 9.0.12
+ 2.3.0.Final
-
- ${maven.min.version}
-
+
+
+ central
+ Central Repository
+
+ https://repo.maven.apache.org/maven2
+
+
+ true
+
+
+ false
+
+
+
+
+ payara-milestones
+ Payara Milestones
+ https://raw.github.com/payara/Payara_PatchedProjects/master
+
+ true
+
+
+ false
+
+
+
+
+ ossrh
+ Sonatype-snapshot
+ https://oss.sonatype.org/content/repositories/snapshots
+
+ false
+
+
+ true
+
+
+
+
+ jvnet-nexus-staging
+ Java.net Staging Repositories
+ https://maven.java.net/content/repositories/staging
+
+ true
+
+
+ false
+
+
+
+
+ jvnet-nexus-promoted
+ Java.net Promoted Repositories
+ https://maven.java.net/content/repositories/promoted/
+
+ true
+
+
+ false
+
+
+
+
+
+ test-utils
+ servlet
+ jaxrs
+ security
+ jsfcdi
+ jpa
+ jsonb
+ validation
+ json-p
@@ -30,26 +117,43 @@
org.jboss.arquillianarquillian-bom
- 1.1.5.Final
+ 1.4.1.Finalimportpom
+
+ org.jboss.arquillian.container
+ arquillian-container-test-api
+ 1.4.1.Final
+
+
+ com.h2database
+ h2
+ 1.4.197
+
+
+ fish.payara.arquillian
+ payara-client-ee8
+ 1.0.Beta3
+ test
+
+
javaxjavaee-api
- 7.0
+ 8.0provided
-
+
+
- org.jboss.weld
- weld-api
- 3.0.Alpha3
- provided
+ javax.annotation
+ javax.annotation-api
+ 1.3.1
@@ -57,7 +161,19 @@
junitjunit
- 4.11
+ 4.12
+ test
+
+
+ org.hamcrest
+ hamcrest-core
+ 2.1
+ test
+
+
+ org.hamcrest
+ hamcrest-library
+ 2.1test
@@ -65,70 +181,137 @@
arquillian-junit-containertest
+
+ org.jboss.arquillian.protocol
+ arquillian-protocol-servlet
+ test
+ org.jboss.shrinkwrap.resolvershrinkwrap-resolver-impl-maventest
+ jarorg.jboss.shrinkwrap.resolvershrinkwrap-resolver-impl-maven-archivetest
+
+ xmlunit
+ xmlunit
+ 1.6
+ test
+
+
+ org.skyscreamer
+ jsonassert
+ 1.5.0
+ test
+
+
+ net.sourceforge.htmlunit
+ htmlunit
+ 2.33
+ test
+
+
+ rhino
+ js
+ 1.7R2
+ test
+
+
+ org.json
+ json
+ 20180813
+ test
+
+
+ com.jayway.awaitility
+ awaitility
+ 1.7.0
+ test
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+ 2.3.2
+
+
+ org.glassfish.jaxb
+ jaxb-runtime
+ 2.3.2
+
+
${project.artifactId}
+
src/test/resourcestrue
+
org.apache.maven.pluginsmaven-compiler-plugin
- 3.1
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 2.17
-
-
- default-test
- test
-
- test
-
-
-
+ 3.8.0
+
+ ${java.min.version}
+ ${java.min.version}
+ org.apache.maven.pluginsmaven-surefire-report-plugin
- 2.17
+ 3.0.0-M3truetrue
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.0.0-M3
+
+
+ javax.annotation
+ javax.annotation-api
+ 1.3.2
+
+
+ org.apache.maven.pluginsmaven-war-plugin
- 2.5
+ 3.2.2
+
+ true
+ false
+
+ org.apache.maven.pluginsmaven-enforcer-plugin
+ 3.0.0-M2
- At least Maven in version ${maven.min.version} is required.
+ At least Maven in version
+ ${maven.min.version} is
+ required.${maven.min.version}
- At least a JDK in version ${java.min.version} is required.
+ At least a JDK in version
+ ${java.min.version} is
+ required.${java.min.version}
@@ -142,84 +325,629 @@
- org.wildfly.plugins
- wildfly-maven-plugin
- 1.0.2.Final
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 3.1.1
-
-
-
-
- org.apache.maven.plugins
- maven-enforcer-plugin
- ${plugin.enforcer.version}
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-report-plugin
- 2.18
-
- true
- true
-
+ maven-resources-plugin
+ 3.1.0
-
+
+
+
+
+
+
+
+
+
- wildfly-remote-arquillian
+ payara-ci-managed
+
true
+
+
+
+ fish.payara.arquillian
+ payara-client-ee8
+
+
+
+
+ fish.payara.arquillian
+ arquillian-payara-server-4-managed
+ 1.0.Beta3-m3
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ unpack
+ process-test-classes
+
+ unpack
+
+
+ ${session.executionRootDirectory}/target
+ ${session.executionRootDirectory}/target/dependency-maven-plugin-markers
+
+
+ fish.payara.distributions
+ payara
+ ${payara.version}
+ zip
+ false
+ ${session.executionRootDirectory}/target
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+
+ javax.annotation
+ javax.annotation-api
+ 1.3.1
+
+
+
+ ${session.executionRootDirectory}/target/payara5
+
+
+
+ ${session.executionRootDirectory}/target/payara5
+ payara-remote
+ payara
+
+
+
+
+
+
+
+
+ payara-embedded
+
+
+ fish.payara.extras
+ payara-embedded-all
+ ${payara.version}
+ test
+
+
+ org.glassfish
+ javax.json
+ 1.0.4
+ test
+
+
+ org.glassfish.tyrus
+ tyrus-client
+ 1.13.1
+ test
+
+
+ org.glassfish.tyrus
+ tyrus-container-grizzly-client
+ 1.13.1
+ test
+
+
+ org.glassfish.jersey.core
+ jersey-client
+ 2.27
+ test
+
+
+
+ org.jboss.arquillian.container
+ arquillian-glassfish-embedded-3.1
+ 1.0.0.Final
+ test
+
+
+
+
+
+ src/test/resources
+ true
+
+
+ src/test/resources-glassfish-embedded
+ true
+
+
+
+
+
+
+ payara-remote
+
+
+
+ fish.payara.arquillian
+ payara-client-ee8
+
+
+
+
+ fish.payara.arquillian
+ arquillian-payara-server-4-remote
+ 1.0.Beta3-m3
+ test
+
+
+
+
+
+ maven-surefire-plugin
+
+
+ payara-remote
+
+
+
+
+
+
+
+
+
+ payara-micro-managed
+
+
+ true
+ true
+
+
+
+
+ fish.payara.arquillian
+ payara-client-ee8
+
+
+
+
+ fish.payara.arquillian
+ arquillian-payara-micro-5-managed
+ 1.0.Beta3-m4
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ process-test-classes
+
+ copy
+
+
+
+
+ fish.payara.extras
+ payara-micro
+ ${payara.micro.version}
+ false
+ ${session.executionRootDirectory}/target/
+ payara-micro-${payara.micro.version}.jar
+
+
+
+
+
+
+
+
+ maven-surefire-plugin
+
+
+ ${session.executionRootDirectory}/target/payara-micro-${payara.micro.version}.jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+ glassfish-embedded
+
+
+ org.glassfish.main.extras
+ glassfish-embedded-all
+ ${glassfish.version}
+ test
+
+
+ org.glassfish
+ javax.json
+ 1.0.4
+ test
+
+
+ org.glassfish.tyrus
+ tyrus-client
+ 1.3
+ test
+
+
+ org.glassfish.tyrus
+ tyrus-container-grizzly-client
+ 1.3
+ test
+
+
+ org.glassfish.jersey.core
+ jersey-client
+ 2.7
+ test
+
+
+ org.jboss.arquillian.container
+ arquillian-glassfish-embedded-3.1
+ 1.0.2
+ test
+
+
+
+
+
+ src/test/resources
+ true
+
+
+ src/test/resources-glassfish-embedded
+ true
+
+
+
+
+
+
+ glassfish-remote
+
+
+ org.glassfish
+ javax.json
+ 1.0.4
+ test
+
+
+ org.glassfish.tyrus
+ tyrus-client
+ 1.3
+ test
+
- io.undertow
- undertow-websockets-jsr
- 1.0.0.Beta33
+ org.glassfish.tyrus
+ tyrus-container-grizzly-client
+ 1.3test
-
+
- org.wildfly
- wildfly-arquillian-container-remote
- 8.2.0.Final
+ org.jboss.arquillian.container
+ arquillian-glassfish-remote-3.1
+ 1.0.2test
+
+
+ maven-surefire-plugin
+
+
+ glassfish-remote
+
+
+
+ src/test/resourcestrue
+
+ src/test/resources-glassfish-remote
+ true
+
+
+
+
+
+
+
+
+ tomcat-remote
+
+
+ true
+ true
+ true
+ true
+
+
+
+
+ org.jboss.arquillian.container
+ arquillian-tomcat-remote-7
+ 1.1.0.Final
+ test
+
+
+
+
+
+
+ maven-surefire-plugin
+
+
+ tomcat-remote
+
+
+
+
+
+
+
+
+
+ tomcat-ci-managed
+
+
+ true
+ true
+ true
+ true
+
+
+
+
+
+ apache.public
+ https://repository.apache.org/content/repositories/public/
+
+ true
+
+
+ false
+
+
+
+
+
+ apache.staging
+ https://repository.apache.org/content/repositories/staging/
+
+ true
+
+
+ false
+
+
+
+
+
+
+ org.jboss.arquillian.container
+ arquillian-tomcat-managed-7
+ 1.1.0.Final
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ unpack-tomcat
+ process-test-classes
+
+ unpack
+
+
+
+
+ org.apache.tomcat
+ tomcat
+ ${tomcat.version}
+ zip
+ false
+ ${project.build.directory}
+
+
+
+
+
+ unpack-tomcat-users
+ process-test-classes
+
+ unpack
+
+
+
+
+ org.javaee8
+ test-utils
+ ${project.version}
+ jar
+ true
+ ${project.build.directory}/apache-tomcat-${tomcat.version}/conf
+ tomcat-users.xml
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ tomcat-ci-managed
+ ${project.build.directory}/apache-tomcat-${tomcat.version}
+
+
+
+
+
+
+
+
+
+
+ thorntail
+
+
+
+
+ io.thorntail
+ bom
+ ${thorntail.version}
+ import
+ pom
+
+
+
+
+
+
+
+
+ fish.payara.arquillian
+ payara-client-ee8
+
+
+
+ io.thorntail
+ arquillian
+ test
+
+
+
+
+
+
+
+ maven-surefire-plugin
+
+
+
+
+ arquillian-thorntail.xml
+
+
+
+
+
+
+
+
+
+
+ javadocs
+
+
+ javadocs
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 2.4
+
+
+ sources
+ process-resources
+
+ sources
+ resolve
+
+
+ javadoc
+ false
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-report-plugin
+ 2.19.1
+
+ true
+ true
+
+
+
+
+
diff --git a/security/README.md b/security/README.md
new file mode 100644
index 00000000..e13fbb0a
--- /dev/null
+++ b/security/README.md
@@ -0,0 +1,10 @@
+# Java EE 8 Samples: Security 1.0#
+
+The [JSR 375](https://jcp.org/en/jsr/detail?id=375) specifies the first version of the Java EE Security API - Java EE Security 1.0.
+
+## Samples ##
+
+ - dynamic-rememberme
+ - jwt
+
+
diff --git a/security/dynamic-rememberme/pom.xml b/security/dynamic-rememberme/pom.xml
new file mode 100644
index 00000000..50a7990b
--- /dev/null
+++ b/security/dynamic-rememberme/pom.xml
@@ -0,0 +1,14 @@
+
+4.0.0
+
+
+ org.javaee8
+ security
+ 1.0-SNAPSHOT
+
+
+ dynamic-rememberme
+ war
+ Java EE 8 Samples: Security - Dynamic Remember-me
+
+
diff --git a/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/ApplicationInit.java b/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/ApplicationInit.java
new file mode 100644
index 00000000..4427b0ec
--- /dev/null
+++ b/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/ApplicationInit.java
@@ -0,0 +1,82 @@
+package org.javaee8.security.dynamic.rememberme;
+
+import static java.util.stream.Collectors.toSet;
+
+import java.util.logging.Logger;
+
+import javax.annotation.Priority;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Alternative;
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.InterceptionFactory;
+import javax.security.enterprise.authentication.mechanism.http.BasicAuthenticationMechanismDefinition;
+import javax.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism;
+
+import org.javaee8.security.dynamic.rememberme.util.HttpAuthenticationMechanismWrapper;
+import org.javaee8.security.dynamic.rememberme.util.RememberMeAnnotationLiteral;
+
+// Configure a basic authentication mechanism.
+// In this sample we'll dynamically apply the @RememberMe annotation to this.
+@BasicAuthenticationMechanismDefinition(
+ realmName="foo"
+)
+
+@Alternative
+@Priority(500)
+@ApplicationScoped
+public class ApplicationInit {
+
+ private static final Logger logger = Logger.getLogger(ApplicationInit.class.getName());
+
+ @Produces
+ public HttpAuthenticationMechanism produce(InterceptionFactory interceptionFactory, BeanManager beanManager) {
+
+ logger.info("Producing wrapped and dynamic proxied mechanism");
+
+ // Get a reference (instance) to the HttpAuthenticationMechanism that would have been
+ // used had we not provided an alternative here.
+ //
+ // In this sample that is the mechanism
+ // the container puts into service following the @BasicAuthenticationMechanismDefinition
+ // used above.
+ HttpAuthenticationMechanism mechanism =
+ createRef(
+ beanManager.resolve(
+ beanManager.getBeans(HttpAuthenticationMechanism.class)
+ .stream()
+ .filter(e -> !e.getBeanClass().equals(ApplicationInit.class))
+ .collect(toSet())), beanManager);
+
+ // We're telling the InterceptionFactory here to dynamically add the @RememberMeAnnotation
+ // annotation with the supplied values.
+ interceptionFactory.configure().add(
+ new RememberMeAnnotationLiteral(
+ 86400, "",
+ false, "",
+ true, "",
+ "JREMEMBERMEID",
+ true, ""
+ )
+ );
+
+ // This will create a proxy as configured above around an instance of
+ // HttpAuthenticationMechanismWrapper that we provide.
+
+ // Note that we provide an extra wrapper since unfortunately createInterceptedInstance
+ // says:
+ // "If the provided instance is an internal container construct (such as client proxy), non-portable behavior results."
+ return interceptionFactory.createInterceptedInstance(
+ new HttpAuthenticationMechanismWrapper(mechanism));
+ }
+
+ HttpAuthenticationMechanism createRef(Bean> bean, BeanManager beanManager) {
+ return (HttpAuthenticationMechanism)
+ beanManager.getReference(
+ bean,
+ HttpAuthenticationMechanism.class,
+ beanManager.createCreationalContext(bean));
+ }
+
+}
diff --git a/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/Servlet.java b/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/Servlet.java
new file mode 100644
index 00000000..59775edd
--- /dev/null
+++ b/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/Servlet.java
@@ -0,0 +1,34 @@
+package org.javaee8.security.dynamic.rememberme;
+
+import java.io.IOException;
+
+import javax.annotation.security.DeclareRoles;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.HttpConstraint;
+import javax.servlet.annotation.ServletSecurity;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@DeclareRoles({ "architect", "admin" })
+@WebServlet("/servlet")
+@ServletSecurity(@HttpConstraint(rolesAllowed = "architect"))
+public class Servlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+
+ String webName = null;
+ if (request.getUserPrincipal() != null) {
+ webName = request.getUserPrincipal().getName();
+ }
+
+ response.getWriter().write("web username: " + webName + "\n");
+ response.getWriter().write("web user has role \"architect\": " + request.isUserInRole("architect") + "\n");
+
+ }
+
+}
diff --git a/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/TestIdentityStore.java b/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/TestIdentityStore.java
new file mode 100644
index 00000000..56180e13
--- /dev/null
+++ b/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/TestIdentityStore.java
@@ -0,0 +1,25 @@
+package org.javaee8.security.dynamic.rememberme;
+
+import static java.util.Arrays.asList;
+import static javax.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
+
+import java.util.HashSet;
+
+import javax.enterprise.context.RequestScoped;
+import javax.security.enterprise.credential.UsernamePasswordCredential;
+import javax.security.enterprise.identitystore.CredentialValidationResult;
+import javax.security.enterprise.identitystore.IdentityStore;
+
+@RequestScoped
+public class TestIdentityStore implements IdentityStore {
+
+ public CredentialValidationResult validate(UsernamePasswordCredential credential) {
+
+ if (!(credential.getCaller().equals("test") && credential.getPassword().compareTo("pass"))) {
+ return INVALID_RESULT;
+ }
+
+ return new CredentialValidationResult("test", new HashSet<>(asList("architect", "admin")));
+ }
+
+}
diff --git a/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/TestRememberMeIdentityStore.java b/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/TestRememberMeIdentityStore.java
new file mode 100644
index 00000000..ba2a2f76
--- /dev/null
+++ b/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/TestRememberMeIdentityStore.java
@@ -0,0 +1,44 @@
+package org.javaee8.security.dynamic.rememberme;
+
+
+import static javax.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.security.enterprise.CallerPrincipal;
+import javax.security.enterprise.credential.RememberMeCredential;
+import javax.security.enterprise.identitystore.CredentialValidationResult;
+import javax.security.enterprise.identitystore.RememberMeIdentityStore;
+
+@ApplicationScoped
+public class TestRememberMeIdentityStore implements RememberMeIdentityStore {
+
+ private Map loginTokens = new ConcurrentHashMap<>();
+
+ @Override
+ public CredentialValidationResult validate(RememberMeCredential credential) {
+ if (!loginTokens.containsKey(credential.getToken())) {
+ return INVALID_RESULT;
+ }
+
+ return loginTokens.get(credential.getToken());
+ }
+
+ @Override
+ public String generateLoginToken(CallerPrincipal callerPrincipal, Set groups) {
+ String token = UUID.randomUUID().toString();
+ loginTokens.put(token, new CredentialValidationResult(callerPrincipal, groups));
+
+ return token;
+ }
+
+ @Override
+ public void removeLoginToken(String token) {
+ loginTokens.remove(token);
+ }
+
+}
diff --git a/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/util/HttpAuthenticationMechanismWrapper.java b/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/util/HttpAuthenticationMechanismWrapper.java
new file mode 100644
index 00000000..1a122e04
--- /dev/null
+++ b/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/util/HttpAuthenticationMechanismWrapper.java
@@ -0,0 +1,42 @@
+package org.javaee8.security.dynamic.rememberme.util;
+
+import javax.security.enterprise.AuthenticationException;
+import javax.security.enterprise.AuthenticationStatus;
+import javax.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism;
+import javax.security.enterprise.authentication.mechanism.http.HttpMessageContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class HttpAuthenticationMechanismWrapper implements HttpAuthenticationMechanism {
+
+ private HttpAuthenticationMechanism httpAuthenticationMechanism;
+
+ public HttpAuthenticationMechanismWrapper() {
+ }
+
+ public HttpAuthenticationMechanismWrapper(HttpAuthenticationMechanism httpAuthenticationMechanism) {
+ this.httpAuthenticationMechanism = httpAuthenticationMechanism;
+ }
+
+ HttpAuthenticationMechanism getWrapped() {
+ return httpAuthenticationMechanism;
+ }
+
+ @Override
+ public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response,
+ HttpMessageContext httpMessageContext) throws AuthenticationException {
+ return getWrapped().validateRequest(request, response, httpMessageContext);
+ }
+
+ @Override
+ public AuthenticationStatus secureResponse(HttpServletRequest request, HttpServletResponse response,
+ HttpMessageContext httpMessageContext) throws AuthenticationException {
+ return getWrapped().secureResponse(request, response, httpMessageContext);
+ }
+
+ @Override
+ public void cleanSubject(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext) {
+ getWrapped().cleanSubject(request, response, httpMessageContext);
+ }
+
+}
\ No newline at end of file
diff --git a/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/util/RememberMeAnnotationLiteral.java b/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/util/RememberMeAnnotationLiteral.java
new file mode 100644
index 00000000..f00f6ef2
--- /dev/null
+++ b/security/dynamic-rememberme/src/main/java/org/javaee8/security/dynamic/rememberme/util/RememberMeAnnotationLiteral.java
@@ -0,0 +1,89 @@
+package org.javaee8.security.dynamic.rememberme.util;
+
+import javax.enterprise.util.AnnotationLiteral;
+import javax.security.enterprise.authentication.mechanism.http.RememberMe;
+
+@SuppressWarnings("all")
+public class RememberMeAnnotationLiteral extends AnnotationLiteral implements RememberMe {
+
+ private static final long serialVersionUID = 1L;
+
+ int cookieMaxAgeSeconds;
+ String cookieMaxAgeSecondsExpression;
+ boolean cookieSecureOnly;
+ String cookieSecureOnlyExpression;
+ boolean cookieHttpOnly;
+ String cookieHttpOnlyExpression;
+ String cookieName;
+ boolean isRememberMe;
+ String isRememberMeExpression;
+
+ public RememberMeAnnotationLiteral(
+
+ int cookieMaxAgeSeconds,
+ String cookieMaxAgeSecondsExpression,
+ boolean cookieSecureOnly,
+ String cookieSecureOnlyExpression,
+ boolean cookieHttpOnly,
+ String cookieHttpOnlyExpression,
+ String cookieName,
+ boolean isRememberMe,
+ String isRememberMeExpression
+
+ ) {
+
+ this.cookieMaxAgeSeconds = cookieMaxAgeSeconds;
+ this.cookieMaxAgeSecondsExpression = cookieMaxAgeSecondsExpression;
+ this.cookieSecureOnly = cookieSecureOnly;
+ this.cookieSecureOnlyExpression = cookieSecureOnlyExpression;
+ this.cookieHttpOnly = cookieHttpOnly;
+ this.cookieHttpOnlyExpression = cookieHttpOnlyExpression;
+ this.cookieName = cookieName;
+ this.isRememberMe = isRememberMe;
+ this.isRememberMeExpression = isRememberMeExpression;
+ }
+
+ @Override
+ public boolean cookieHttpOnly() {
+ return cookieHttpOnly;
+ }
+
+ @Override
+ public String cookieHttpOnlyExpression() {
+ return cookieHttpOnlyExpression;
+ }
+
+ @Override
+ public int cookieMaxAgeSeconds() {
+ return cookieMaxAgeSeconds;
+ }
+
+ @Override
+ public String cookieMaxAgeSecondsExpression() {
+ return cookieMaxAgeSecondsExpression;
+ }
+
+ @Override
+ public boolean cookieSecureOnly() {
+ return cookieSecureOnly;
+ }
+
+ @Override
+ public String cookieSecureOnlyExpression() {
+ return cookieSecureOnlyExpression;
+ }
+
+ @Override
+ public String cookieName() {
+ return cookieName;
+ }
+
+ public boolean isRememberMe() {
+ return isRememberMe;
+ }
+
+ @Override
+ public String isRememberMeExpression() {
+ return isRememberMeExpression;
+ }
+}
\ No newline at end of file
diff --git a/security/dynamic-rememberme/src/test/java/org/javaee8/security/dynamic/rememberme/DynamicRemembermeTest.java b/security/dynamic-rememberme/src/test/java/org/javaee8/security/dynamic/rememberme/DynamicRemembermeTest.java
new file mode 100644
index 00000000..6730258e
--- /dev/null
+++ b/security/dynamic-rememberme/src/test/java/org/javaee8/security/dynamic/rememberme/DynamicRemembermeTest.java
@@ -0,0 +1,160 @@
+package org.javaee8.security.dynamic.rememberme;
+
+import static org.jboss.shrinkwrap.api.ShrinkWrap.create;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.apache.http.client.CredentialsProvider;
+import org.javaee8.security.dynamic.rememberme.util.RememberMeAnnotationLiteral;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.gargoylesoftware.htmlunit.DefaultCredentialsProvider;
+import com.gargoylesoftware.htmlunit.Page;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.util.Cookie;
+
+/**
+ * @author Arjan Tijms
+ */
+@RunWith(Arquillian.class)
+public class DynamicRemembermeTest {
+
+ @ArquillianResource
+ private URL base;
+
+ private WebClient webClient;
+
+ @Deployment
+ public static WebArchive createDeployment() {
+ return
+ create(WebArchive.class)
+ .addClasses(
+ ApplicationInit.class,
+ TestIdentityStore.class,
+ TestRememberMeIdentityStore.class,
+ Servlet.class
+ )
+ .addPackage(
+ RememberMeAnnotationLiteral.class.getPackage())
+ .addAsWebInfResource("jboss-web.xml");
+ }
+
+ @Before
+ public void setup() {
+ webClient = new WebClient();
+ webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
+ }
+
+ @After
+ public void teardown() {
+ webClient.close();
+ }
+
+ @Test
+ @RunAsClient
+ public void testNotAuthenticated() throws IOException {
+ Page page = webClient.getPage(base + "servlet");
+
+ assertEquals(
+ 401,
+ page.getWebResponse().getStatusCode());
+
+ assertTrue(
+ "Response did not contain the \"WWW-Authenticate\" header.",
+ page.getWebResponse().getResponseHeaderValue("WWW-Authenticate") != null);
+ }
+
+ @Test
+ @RunAsClient
+ public void testAuthenticatedOnce() throws IOException {
+
+ // 1. Request the resource with valid HTTP BASIC credentials
+
+ CredentialsProvider originalCredentialsProvider = webClient.getCredentialsProvider();
+ DefaultCredentialsProvider credentialsProvider = new DefaultCredentialsProvider();
+ credentialsProvider.addCredentials("test", "pass");
+
+ webClient.setCredentialsProvider(credentialsProvider);
+
+ Page page = webClient.getPage(base + "servlet");
+
+ // We should now get a 200 (OK) response
+
+ assertEquals(
+ 200,
+ page.getWebResponse().getStatusCode());
+
+ String content = page.getWebResponse().getContentAsString();
+
+ assertTrue(
+ content.contains("web username: test"));
+
+ assertTrue(
+ content.contains("web user has role \"architect\": true"));
+
+
+ // Because remember-me is used, we should have received the JREMEMBERMEID cookie
+
+ Cookie cookie = webClient.getCookieManager().getCookie("JREMEMBERMEID");
+
+ assertNotNull(cookie);
+
+ System.out.println("JREMEMBERMEID cookie: " + cookie);
+
+
+
+ // 2. Request the resource without valid HTTP BASIC credentials
+
+ webClient.setCredentialsProvider(originalCredentialsProvider);
+
+ page = webClient.getPage(base + "servlet");
+
+ // Since our credentials have been exchanged for a token (residing within the cookie)
+ // we should still be able to get a 200
+
+ assertEquals(200, page.getWebResponse().getStatusCode());
+
+ content = page.getWebResponse().getContentAsString();
+
+ assertTrue(
+ content.contains("web username: test"));
+
+ assertTrue(
+ content.contains("web user has role \"architect\": true"));
+
+ System.out.println("\nContent:\n" + content + "\n");
+
+
+
+ // 3. Request the resource without valid HTTP BASIC credentials as well as without the cookie
+
+ webClient.getCookieManager().removeCookie(cookie);
+
+ page = webClient.getPage(base + "servlet");
+
+ // We should not get a 200 okay now but a 401 Unauthorized, and the server should
+ // ask for our credentials again
+
+ assertEquals(
+ 401,
+ page.getWebResponse().getStatusCode());
+
+ assertTrue(
+ "Response did not contain the \"WWW-Authenticate\" header.",
+ page.getWebResponse().getResponseHeaderValue("WWW-Authenticate") != null);
+
+ }
+
+}
diff --git a/security/dynamic-rememberme/src/test/resources/jboss-web.xml b/security/dynamic-rememberme/src/test/resources/jboss-web.xml
new file mode 100644
index 00000000..4a9ba91f
--- /dev/null
+++ b/security/dynamic-rememberme/src/test/resources/jboss-web.xml
@@ -0,0 +1,6 @@
+
+
+
+
+ jaspitest
+
\ No newline at end of file
diff --git a/security/jwt/README.md b/security/jwt/README.md
new file mode 100644
index 00000000..153b2220
--- /dev/null
+++ b/security/jwt/README.md
@@ -0,0 +1,33 @@
+# Java EE Security 1.0 JWT Example
+Sample to demonstrate JWT integration with Java EE Security 1.0 (Soteria RI)
+
+### Sample Users
+Username | Password | Roles
+--- | --- | ---
+payara | fish | ADMIN_ROLE, USER_ROLE
+duke | secret | USER_ROLE
+
+### Login EndPoint
+http://localhost:8080/security-jwt-example/api/auth/login?name=duke&password=secret&rememberme=false
+
+### Protected REST EndPoint
+
+EndPoint | HTTP Method | Roles Allowed
+--- | --- | ---
+http://localhost:8080/security-jwt-example/api/sample/read | GET | ANONYMOUS, USER_ROLE, ADMIN_ROLE
+http://localhost:8080/security-jwt-example/api/sample/write | POST | USER_ROLE, ADMIN_ROLE
+http://localhost:8080/security-jwt-example/api/sample/delete | DELETE | ADMIN_ROLE
+
+
+#### rememberme=false
+
+Whenever the user wants to access a protected resource, the user agent send the JWT in the Authorization header using the Bearer schema. The content of the header should look like the following:
+
+`Authorization: Bearer `
+
+This is a stateless authentication mechanism as the user state is never saved in server memory.
+The server's protected routes will check for a valid JWT in the Authorization header, and if it's present, the user will be allowed to access protected resources.
+
+#### rememberme=true
+Whenever the user wants to access a protected resource, the user agent would automatically include the JWT in the cookie with `JREMEMBERMEID` key.
+It does not require state to be stored on the server because the JWT encapsulates everything the server needs to serve the request.
\ No newline at end of file
diff --git a/security/jwt/pom.xml b/security/jwt/pom.xml
new file mode 100644
index 00000000..f45387f1
--- /dev/null
+++ b/security/jwt/pom.xml
@@ -0,0 +1,27 @@
+
+
+ 4.0.0
+
+
+ org.javaee8
+ security
+ 1.0-SNAPSHOT
+
+
+ jwt
+ war
+ Java EE 8 Samples: Security - JWT
+
+
+ 0.6.0
+
+
+
+
+ io.jsonwebtoken
+ jjwt
+ ${version.jsonwebtoken}
+
+
+
+
diff --git a/security/jwt/src/main/java/org/javaee8/security/jwt/AuthenticationIdentityStore.java b/security/jwt/src/main/java/org/javaee8/security/jwt/AuthenticationIdentityStore.java
new file mode 100644
index 00000000..a5bc602d
--- /dev/null
+++ b/security/jwt/src/main/java/org/javaee8/security/jwt/AuthenticationIdentityStore.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2017 Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package org.javaee8.security.jwt;
+
+import static java.util.Collections.singleton;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.RequestScoped;
+import javax.security.enterprise.credential.Credential;
+import javax.security.enterprise.credential.UsernamePasswordCredential;
+import javax.security.enterprise.identitystore.CredentialValidationResult;
+import static javax.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
+import static javax.security.enterprise.identitystore.CredentialValidationResult.NOT_VALIDATED_RESULT;
+import javax.security.enterprise.identitystore.IdentityStore;
+import javax.security.enterprise.identitystore.IdentityStore.ValidationType;
+import static javax.security.enterprise.identitystore.IdentityStore.ValidationType.VALIDATE;
+
+@RequestScoped
+public class AuthenticationIdentityStore implements IdentityStore {
+
+ private Map callerToPassword;
+
+ @PostConstruct
+ public void init() {
+ callerToPassword = new HashMap<>();
+ callerToPassword.put("payara", "fish");
+ callerToPassword.put("duke", "secret");
+ }
+
+ @Override
+ public CredentialValidationResult validate(Credential credential) {
+ CredentialValidationResult result;
+
+ if (credential instanceof UsernamePasswordCredential) {
+ UsernamePasswordCredential usernamePassword = (UsernamePasswordCredential) credential;
+ String expectedPW = callerToPassword.get(usernamePassword.getCaller());
+ if (expectedPW != null && expectedPW.equals(usernamePassword.getPasswordAsString())) {
+ result = new CredentialValidationResult(usernamePassword.getCaller());
+ } else {
+ result = INVALID_RESULT;
+ }
+ } else {
+ result = NOT_VALIDATED_RESULT;
+ }
+ return result;
+ }
+
+ @Override
+ public Set validationTypes() {
+ return singleton(VALIDATE);
+ }
+}
diff --git a/security/jwt/src/main/java/org/javaee8/security/jwt/AuthorizationIdentityStore.java b/security/jwt/src/main/java/org/javaee8/security/jwt/AuthorizationIdentityStore.java
new file mode 100644
index 00000000..8fecbeb0
--- /dev/null
+++ b/security/jwt/src/main/java/org/javaee8/security/jwt/AuthorizationIdentityStore.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2017 Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package org.javaee8.security.jwt;
+
+import static org.javaee8.security.jwt.Constants.ADMIN;
+import static org.javaee8.security.jwt.Constants.USER;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.RequestScoped;
+import javax.security.enterprise.identitystore.CredentialValidationResult;
+import javax.security.enterprise.identitystore.IdentityStore;
+import javax.security.enterprise.identitystore.IdentityStore.ValidationType;
+import static javax.security.enterprise.identitystore.IdentityStore.ValidationType.PROVIDE_GROUPS;
+
+@RequestScoped
+public class AuthorizationIdentityStore implements IdentityStore {
+
+ private Map> groupsPerCaller;
+
+ @PostConstruct
+ public void init() {
+ groupsPerCaller = new HashMap<>();
+ groupsPerCaller.put("payara", new HashSet<>(asList(USER, ADMIN)));
+ groupsPerCaller.put("duke", singleton(USER));
+ }
+
+ @Override
+ public Set getCallerGroups(CredentialValidationResult validationResult) {
+ Set result = groupsPerCaller.get(validationResult.getCallerPrincipal().getName());
+ if (result == null) {
+ result = emptySet();
+ }
+ return result;
+ }
+
+ @Override
+ public Set validationTypes() {
+ return singleton(PROVIDE_GROUPS);
+ }
+
+}
diff --git a/security/jwt/src/main/java/org/javaee8/security/jwt/Constants.java b/security/jwt/src/main/java/org/javaee8/security/jwt/Constants.java
new file mode 100644
index 00000000..d7b1947f
--- /dev/null
+++ b/security/jwt/src/main/java/org/javaee8/security/jwt/Constants.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2017 Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package org.javaee8.security.jwt;
+
+public final class Constants {
+
+ public static final String AUTHORIZATION_HEADER = "Authorization";
+
+ public static final String BEARER = "Bearer ";
+
+ public static final String ADMIN = "ROLE_ADMIN";
+
+ public static final String USER = "ROLE_USER";
+
+ public static final int REMEMBERME_VALIDITY_SECONDS = 24 * 60 * 60; //24 hours
+
+ // Load the TokenProvider configuration[secretKey,tokenValidity] and REMEMBERME_VALIDITY_SECONDS from config
+}
diff --git a/security/jwt/src/main/java/org/javaee8/security/jwt/JWTAuthenticationMechanism.java b/security/jwt/src/main/java/org/javaee8/security/jwt/JWTAuthenticationMechanism.java
new file mode 100644
index 00000000..fbd7bbdc
--- /dev/null
+++ b/security/jwt/src/main/java/org/javaee8/security/jwt/JWTAuthenticationMechanism.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2017 Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package org.javaee8.security.jwt;
+
+import static org.javaee8.security.jwt.Constants.AUTHORIZATION_HEADER;
+import static org.javaee8.security.jwt.Constants.BEARER;
+import static org.javaee8.security.jwt.Constants.REMEMBERME_VALIDITY_SECONDS;
+import io.jsonwebtoken.ExpiredJwtException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.enterprise.context.RequestScoped;
+import javax.inject.Inject;
+import javax.security.enterprise.AuthenticationStatus;
+import javax.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism;
+import javax.security.enterprise.authentication.mechanism.http.HttpMessageContext;
+import javax.security.enterprise.authentication.mechanism.http.RememberMe;
+import javax.security.enterprise.credential.UsernamePasswordCredential;
+import javax.security.enterprise.identitystore.CredentialValidationResult;
+import javax.security.enterprise.identitystore.IdentityStoreHandler;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@RememberMe(
+ cookieMaxAgeSeconds = REMEMBERME_VALIDITY_SECONDS,
+ isRememberMeExpression = "self.isRememberMe(httpMessageContext)"
+)
+@RequestScoped
+public class JWTAuthenticationMechanism implements HttpAuthenticationMechanism {
+
+ private static final Logger LOGGER = Logger.getLogger(JWTAuthenticationMechanism.class.getName());
+
+ /**
+ * Access to the
+ * IdentityStore(AuthenticationIdentityStore,AuthorizationIdentityStore) is
+ * abstracted by the IdentityStoreHandler to allow for multiple identity
+ * stores to logically act as a single IdentityStore
+ */
+ @Inject
+ private IdentityStoreHandler identityStoreHandler;
+
+ @Inject
+ private TokenProvider tokenProvider;
+
+ @Override
+ public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext context) {
+
+ LOGGER.log(Level.INFO, "validateRequest: {0}", request.getRequestURI());
+ // Get the (caller) name and password from the request
+ // NOTE: This is for the smallest possible example only. In practice
+ // putting the password in a request query parameter is highly insecure
+ String name = request.getParameter("name");
+ String password = request.getParameter("password");
+ String token = extractToken(context);
+
+ if (name != null && password != null) {
+ LOGGER.log(Level.INFO, "credentials : {0}, {1}", new String[]{name, password});
+ // validation of the credential using the identity store
+ CredentialValidationResult result = identityStoreHandler.validate(new UsernamePasswordCredential(name, password));
+ if (result.getStatus() == CredentialValidationResult.Status.VALID) {
+ // Communicate the details of the authenticated user to the container and return SUCCESS.
+ return createToken(result, context);
+ }
+ // if the authentication failed, we return the unauthorized status in the http response
+ return context.responseUnauthorized();
+ } else if (token != null) {
+ // validation of the jwt credential
+ return validateToken(token, context);
+ } else if (context.isProtected()) {
+ // A protected resource is a resource for which a constraint has been defined.
+ // if there are no credentials and the resource is protected, we response with unauthorized status
+ return context.responseUnauthorized();
+ }
+ // there are no credentials AND the resource is not protected,
+ // SO Instructs the container to "do nothing"
+ return context.doNothing();
+ }
+
+ /**
+ * To validate the JWT token e.g Signature check, JWT claims
+ * check(expiration) etc
+ *
+ * @param token The JWT access tokens
+ * @param context
+ * @return the AuthenticationStatus to notify the container
+ */
+ private AuthenticationStatus validateToken(String token, HttpMessageContext context) {
+ try {
+ if (tokenProvider.validateToken(token)) {
+ JWTCredential credential = tokenProvider.getCredential(token);
+ return context.notifyContainerAboutLogin(credential.getPrincipal(), credential.getAuthorities());
+ }
+ // if token invalid, response with unauthorized status
+ return context.responseUnauthorized();
+ } catch (ExpiredJwtException eje) {
+ LOGGER.log(Level.INFO, "Security exception for user {0} - {1}", new String[]{eje.getClaims().getSubject(), eje.getMessage()});
+ return context.responseUnauthorized();
+ }
+ }
+
+ /**
+ * Create the JWT using CredentialValidationResult received from
+ * IdentityStoreHandler
+ *
+ * @param result the result from validation of UsernamePasswordCredential
+ * @param context
+ * @return the AuthenticationStatus to notify the container
+ */
+ private AuthenticationStatus createToken(CredentialValidationResult result, HttpMessageContext context) {
+ if (!isRememberMe(context)) {
+ String jwt = tokenProvider.createToken(result.getCallerPrincipal().getName(), result.getCallerGroups(), false);
+ context.getResponse().setHeader(AUTHORIZATION_HEADER, BEARER + jwt);
+ }
+ return context.notifyContainerAboutLogin(result.getCallerPrincipal(), result.getCallerGroups());
+ }
+
+ /**
+ * To extract the JWT from Authorization HTTP header
+ *
+ * @param context
+ * @return The JWT access tokens
+ */
+ private String extractToken(HttpMessageContext context) {
+ String authorizationHeader = context.getRequest().getHeader(AUTHORIZATION_HEADER);
+ if (authorizationHeader != null && authorizationHeader.startsWith(BEARER)) {
+ String token = authorizationHeader.substring(BEARER.length(), authorizationHeader.length());
+ return token;
+ }
+ return null;
+ }
+
+ /**
+ * this function invoked using RememberMe.isRememberMeExpression EL
+ * expression
+ *
+ * @param context
+ * @return The remember me flag
+ */
+ public Boolean isRememberMe(HttpMessageContext context) {
+ return Boolean.valueOf(context.getRequest().getParameter("rememberme"));
+ }
+
+}
diff --git a/security/jwt/src/main/java/org/javaee8/security/jwt/JWTCredential.java b/security/jwt/src/main/java/org/javaee8/security/jwt/JWTCredential.java
new file mode 100644
index 00000000..260b0517
--- /dev/null
+++ b/security/jwt/src/main/java/org/javaee8/security/jwt/JWTCredential.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017 Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package org.javaee8.security.jwt;
+
+import java.util.Set;
+import javax.security.enterprise.credential.Credential;
+
+public class JWTCredential implements Credential {
+
+ private final String principal;
+ private final Set authorities;
+
+ public JWTCredential(String principal, Set authorities) {
+ this.principal = principal;
+ this.authorities = authorities;
+ }
+
+ public String getPrincipal() {
+ return principal;
+ }
+
+ public Set getAuthorities() {
+ return authorities;
+ }
+
+}
diff --git a/security/jwt/src/main/java/org/javaee8/security/jwt/JWTRememberMeIdentityStore.java b/security/jwt/src/main/java/org/javaee8/security/jwt/JWTRememberMeIdentityStore.java
new file mode 100644
index 00000000..fc156c55
--- /dev/null
+++ b/security/jwt/src/main/java/org/javaee8/security/jwt/JWTRememberMeIdentityStore.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2017 Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package org.javaee8.security.jwt;
+
+import io.jsonwebtoken.ExpiredJwtException;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.security.enterprise.CallerPrincipal;
+import javax.security.enterprise.credential.RememberMeCredential;
+import javax.security.enterprise.identitystore.CredentialValidationResult;
+import static javax.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
+import javax.security.enterprise.identitystore.RememberMeIdentityStore;
+
+@ApplicationScoped
+public class JWTRememberMeIdentityStore implements RememberMeIdentityStore {
+
+ private static final Logger LOGGER = Logger.getLogger(JWTRememberMeIdentityStore.class.getName());
+
+ @Inject
+ private TokenProvider tokenProvider;
+
+ @Override
+ public CredentialValidationResult validate(RememberMeCredential rememberMeCredential) {
+ try {
+ if (tokenProvider.validateToken(rememberMeCredential.getToken())) {
+ JWTCredential credential = tokenProvider.getCredential(rememberMeCredential.getToken());
+ return new CredentialValidationResult(credential.getPrincipal(), credential.getAuthorities());
+ }
+ // if token invalid, response with invalid result status
+ return INVALID_RESULT;
+ } catch (ExpiredJwtException eje) {
+ LOGGER.log(Level.INFO, "Security exception for user {0} - {1}", new Object[]{eje.getClaims().getSubject(), eje.getMessage()});
+ return INVALID_RESULT;
+ }
+ }
+
+ @Override
+ public String generateLoginToken(CallerPrincipal callerPrincipal, Set groups) {
+ return tokenProvider.createToken(callerPrincipal.getName(), groups, true);
+ }
+
+ @Override
+ public void removeLoginToken(String token) {
+ // Stateless authentication means at server side we don't maintain the state
+ }
+
+}
diff --git a/security/jwt/src/main/java/org/javaee8/security/jwt/TokenProvider.java b/security/jwt/src/main/java/org/javaee8/security/jwt/TokenProvider.java
new file mode 100644
index 00000000..216ee8c0
--- /dev/null
+++ b/security/jwt/src/main/java/org/javaee8/security/jwt/TokenProvider.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2017 Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package org.javaee8.security.jwt;
+
+import static org.javaee8.security.jwt.Constants.REMEMBERME_VALIDITY_SECONDS;
+import io.jsonwebtoken.*;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import static java.util.stream.Collectors.joining;
+import javax.annotation.PostConstruct;
+
+public class TokenProvider {
+
+ private static final Logger LOGGER = Logger.getLogger(TokenProvider.class.getName());
+
+ private static final String AUTHORITIES_KEY = "auth";
+
+ private String secretKey;
+
+ private long tokenValidity;
+
+ private long tokenValidityForRememberMe;
+
+ @PostConstruct
+ public void init() {
+ // load from config
+ this.secretKey = "my-secret-jwt-key";
+ this.tokenValidity = TimeUnit.HOURS.toMillis(10); //10 hours
+ this.tokenValidityForRememberMe = TimeUnit.SECONDS.toMillis(REMEMBERME_VALIDITY_SECONDS); //24 hours
+ }
+
+ public String createToken(String username, Set authorities, Boolean rememberMe) {
+ long now = (new Date()).getTime();
+ long validity = rememberMe ? tokenValidityForRememberMe : tokenValidity;
+
+ return Jwts.builder()
+ .setSubject(username)
+ .claim(AUTHORITIES_KEY, authorities.stream().collect(joining(",")))
+ .signWith(SignatureAlgorithm.HS512, secretKey)
+ .setExpiration(new Date(now + validity))
+ .compact();
+ }
+
+ public JWTCredential getCredential(String token) {
+ Claims claims = Jwts.parser()
+ .setSigningKey(secretKey)
+ .parseClaimsJws(token)
+ .getBody();
+
+ Set authorities
+ = Arrays.asList(claims.get(AUTHORITIES_KEY).toString().split(","))
+ .stream()
+ .collect(Collectors.toSet());
+
+ return new JWTCredential(claims.getSubject(), authorities);
+ }
+
+ public boolean validateToken(String authToken) {
+ try {
+ Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken);
+ return true;
+ } catch (SignatureException e) {
+ LOGGER.log(Level.INFO, "Invalid JWT signature: {0}", e.getMessage());
+ return false;
+ }
+ }
+}
diff --git a/security/jwt/src/main/java/org/javaee8/security/jwt/rest/ApplicationConfig.java b/security/jwt/src/main/java/org/javaee8/security/jwt/rest/ApplicationConfig.java
new file mode 100644
index 00000000..bb39382a
--- /dev/null
+++ b/security/jwt/src/main/java/org/javaee8/security/jwt/rest/ApplicationConfig.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2017 Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package org.javaee8.security.jwt.rest;
+
+import static org.javaee8.security.jwt.Constants.ADMIN;
+import static org.javaee8.security.jwt.Constants.USER;
+import javax.annotation.security.DeclareRoles;
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+@DeclareRoles({ADMIN, USER})
+@ApplicationPath(value = "api")
+public class ApplicationConfig extends Application {
+}
diff --git a/security/jwt/src/main/java/org/javaee8/security/jwt/rest/AuthController.java b/security/jwt/src/main/java/org/javaee8/security/jwt/rest/AuthController.java
new file mode 100644
index 00000000..6f1f2730
--- /dev/null
+++ b/security/jwt/src/main/java/org/javaee8/security/jwt/rest/AuthController.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2017 Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package org.javaee8.security.jwt.rest;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.inject.Inject;
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.security.enterprise.SecurityContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+import static javax.ws.rs.core.Response.Status.UNAUTHORIZED;
+
+@Path("auth")
+public class AuthController {
+
+ private static final Logger LOGGER = Logger.getLogger(AuthController.class.getName());
+
+ @Inject
+ private SecurityContext securityContext;
+
+ @GET
+ @Path("login")
+ public Response login() {
+ LOGGER.log(Level.INFO, "login");
+ if (securityContext.getCallerPrincipal() != null) {
+ JsonObject result = Json.createObjectBuilder()
+ .add("user", securityContext.getCallerPrincipal().getName())
+ .build();
+ return Response.ok(result).build();
+ }
+ return Response.status(UNAUTHORIZED).build();
+ }
+
+}
diff --git a/security/jwt/src/main/java/org/javaee8/security/jwt/rest/SampleController.java b/security/jwt/src/main/java/org/javaee8/security/jwt/rest/SampleController.java
new file mode 100644
index 00000000..7be05c97
--- /dev/null
+++ b/security/jwt/src/main/java/org/javaee8/security/jwt/rest/SampleController.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2017 Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package org.javaee8.security.jwt.rest;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.security.PermitAll;
+import javax.inject.Inject;
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.security.enterprise.SecurityContext;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+
+@Path("sample")
+public class SampleController {
+
+ private static final Logger LOGGER = Logger.getLogger(SampleController.class.getName());
+
+ @Inject
+ private SecurityContext securityContext;
+
+ @GET
+ @Path("read")
+ @PermitAll
+ public Response read() {
+ LOGGER.log(Level.INFO, "read");
+ JsonObject result = Json.createObjectBuilder()
+ .add("user", securityContext.getCallerPrincipal() != null
+ ? securityContext.getCallerPrincipal().getName() : "Anonymous")
+ .add("message", "Read resource")
+ .build();
+ return Response.ok(result).build();
+ }
+
+ @POST
+ @Path("write")
+// @RolesAllowed({USER, ADMIN})
+ public Response write() {
+ LOGGER.log(Level.INFO, "write");
+ JsonObject result = Json.createObjectBuilder()
+ .add("user", securityContext.getCallerPrincipal().getName())
+ .add("message", "Write resource")
+ .build();
+ return Response.ok(result).build();
+ }
+
+ @DELETE
+ @Path("delete")
+// @RolesAllowed({ADMIN})
+ public Response delete() {
+ LOGGER.log(Level.INFO, "delete");
+ JsonObject result = Json.createObjectBuilder()
+ .add("user", securityContext.getCallerPrincipal().getName())
+ .add("message", "Delete resource")
+ .build();
+ return Response.ok(result).build();
+ }
+}
diff --git a/security/jwt/src/test/java/org/javaee8/security/jwt/JwtTest.java b/security/jwt/src/test/java/org/javaee8/security/jwt/JwtTest.java
new file mode 100644
index 00000000..4958a3fc
--- /dev/null
+++ b/security/jwt/src/test/java/org/javaee8/security/jwt/JwtTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2017 Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package org.javaee8.security.jwt;
+
+import java.io.File;
+import static org.jboss.shrinkwrap.api.ShrinkWrap.create;
+import java.io.IOException;
+import java.net.URL;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import java.net.URISyntaxException;
+import javax.ws.rs.client.ClientBuilder;
+import static javax.ws.rs.client.Entity.json;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import static org.javaee8.security.jwt.Constants.AUTHORIZATION_HEADER;
+import org.javaee8.security.jwt.rest.ApplicationConfig;
+import org.jboss.shrinkwrap.resolver.api.maven.Maven;
+import org.jboss.shrinkwrap.resolver.api.maven.MavenResolverSystem;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+/**
+ * @author Gaurav Gupta
+ */
+@RunWith(Arquillian.class)
+public class JwtTest {
+
+ @ArquillianResource
+ private URL base;
+
+ private WebTarget webTarget;
+
+ @Deployment
+ public static WebArchive createDeployment() {
+ MavenResolverSystem RESOLVER = Maven.resolver();
+ File[] jjwtFiles = RESOLVER.resolve("io.jsonwebtoken:jjwt:0.6.0").withTransitivity().asFile();
+
+ return create(WebArchive.class)
+ .addPackage(ApplicationConfig.class.getPackage())
+ .addPackage(JWTCredential.class.getPackage())
+ .addAsLibraries(jjwtFiles)
+ .setWebXML("web.xml")
+ .addAsWebInfResource("beans.xml")
+ .addAsWebInfResource("jboss-web.xml");
+ }
+
+ @Before
+ public void setup() throws URISyntaxException {
+ webTarget = ClientBuilder.newClient().target(base.toURI().toString() + "api/");
+ }
+
+ @Test
+ @RunAsClient
+ public void testNotAuthenticated() throws IOException {
+ Response response = webTarget
+ .path("auth/login")
+ .queryParam("name", "duke")
+ .queryParam("password", "invalid")
+ .queryParam("rememberme", "false")
+ .request()
+ .get();
+ String authorizationHeader = response.getHeaderString(AUTHORIZATION_HEADER);
+ assertNull(authorizationHeader);
+ assertEquals(
+ 401,
+ response.getStatus());
+ }
+
+ @Test
+ @RunAsClient
+ public void testAuthenticatedWithoutRememberme() throws IOException {
+ Response response = webTarget
+ .path("auth/login")
+ .queryParam("name", "duke")
+ .queryParam("password", "secret")
+ .queryParam("rememberme", "false")
+ .request()
+ .get();
+ String authorizationHeader = response.getHeaderString(AUTHORIZATION_HEADER);
+ assertNotNull(authorizationHeader);
+ assertEquals(
+ 200,
+ response.getStatus());
+
+ Response protectedResponse = webTarget
+ .path("sample/write")
+ .request()
+ .header(AUTHORIZATION_HEADER, authorizationHeader)
+ .post(json(null));
+ assertEquals(
+ 200,
+ protectedResponse.getStatus());
+
+ Response adminResponse = webTarget
+ .path("sample/delete")
+ .request()
+ .header(AUTHORIZATION_HEADER, authorizationHeader)
+ .delete();
+ //Only ROLE_ADMIN user can access
+ assertEquals(
+ 403,
+ adminResponse.getStatus());
+
+ }
+
+ @Test
+ @RunAsClient
+ public void testAuthenticatedWithRememberme() throws IOException {
+ Response response = webTarget
+ .path("auth/login")
+ .queryParam("name", "payara")
+ .queryParam("password", "fish")
+ .queryParam("rememberme", "true")
+ .request()
+ .get();
+ String token = response.getCookies().get("JREMEMBERMEID").getValue();
+ assertNotNull(token);
+ assertEquals(
+ 200,
+ response.getStatus());
+
+ Response protectedResponse = webTarget
+ .path("sample/write")
+ .request()
+ .cookie("JREMEMBERMEID", token)
+ .post(json(null));
+ assertEquals(
+ 200,
+ protectedResponse.getStatus());
+
+ Response adminResponse = webTarget
+ .path("sample/delete")
+ .request()
+ .cookie("JREMEMBERMEID", token)
+ .delete();
+ assertEquals(
+ 200,
+ adminResponse.getStatus());
+
+ }
+}
diff --git a/security/jwt/src/test/resources/beans.xml b/security/jwt/src/test/resources/beans.xml
new file mode 100644
index 00000000..b09e5921
--- /dev/null
+++ b/security/jwt/src/test/resources/beans.xml
@@ -0,0 +1,38 @@
+
+
+
+
diff --git a/security/jwt/src/test/resources/jboss-web.xml b/security/jwt/src/test/resources/jboss-web.xml
new file mode 100644
index 00000000..4a9ba91f
--- /dev/null
+++ b/security/jwt/src/test/resources/jboss-web.xml
@@ -0,0 +1,6 @@
+
+
+
+
+ jaspitest
+
\ No newline at end of file
diff --git a/security/jwt/src/test/resources/web.xml b/security/jwt/src/test/resources/web.xml
new file mode 100644
index 00000000..dbfe954e
--- /dev/null
+++ b/security/jwt/src/test/resources/web.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+ Protected resources
+ /api/sample/write
+
+
+ ROLE_USER
+ ROLE_ADMIN
+
+
+
+
+
+ Admin resources
+ /api/sample/delete
+
+
+ ROLE_ADMIN
+
+
+
diff --git a/security/pom.xml b/security/pom.xml
new file mode 100644
index 00000000..7e7613c9
--- /dev/null
+++ b/security/pom.xml
@@ -0,0 +1,27 @@
+
+4.0.0
+
+
+ org.javaee8
+ samples-parent
+ 1.0-SNAPSHOT
+
+
+ security
+ pom
+
+ Java EE 8 Samples: Security
+
+
+ dynamic-rememberme
+ jwt
+
+
+
+
+ org.javaee8
+ test-utils
+ ${project.version}
+
+
+
diff --git a/servlet/README.md b/servlet/README.md
new file mode 100644
index 00000000..4d060afc
--- /dev/null
+++ b/servlet/README.md
@@ -0,0 +1,9 @@
+# Java EE 8 Samples: Servlet 4.0#
+
+The [JSR 369](https://jcp.org/en/jsr/detail?id=369) specifies the next version of Java Servlets - Java Servlets 4.0.
+
+## Samples ##
+
+ - mapping
+
+
diff --git a/servlet/http2/pom.xml b/servlet/http2/pom.xml
new file mode 100644
index 00000000..cc57a2e2
--- /dev/null
+++ b/servlet/http2/pom.xml
@@ -0,0 +1,508 @@
+
+
+ 4.0.0
+
+
+ org.javaee8
+ servlet
+ 1.0-SNAPSHOT
+
+
+ servlet-http2
+ war
+
+ Java EE 8 Samples: Servlet - http2
+
+
+ 9.4.28.v20200408
+
+
+
+
+
+ org.eclipse.jetty.http2
+ http2-client
+ ${jetty-version}
+ test
+
+
+ org.eclipse.jetty.http2
+ http2-common
+ ${jetty-version}
+ test
+
+
+ org.eclipse.jetty
+ jetty-alpn-openjdk8-client
+ ${jetty-version}
+ test
+
+
+ org.eclipse.jetty
+ jetty-alpn-java-client
+ ${jetty-version}
+ test
+
+
+
+
+
+
+
+ alpn-when-jdk8
+
+ 1.8
+
+
+
+ 8.1.13.v20181017
+
+ ${settings.localRepository}/org/mortbay/jetty/alpn/alpn-boot/${alpn.version}/alpn-boot-${alpn.version}.jar
+
+ -Xbootclasspath/p:${bootclasspathPrefix}
+
+
+
+
+ maven-surefire-plugin
+
+ ${surefireArgLine}
+
+
+
+ org.mortbay.jetty.alpn
+ alpn-boot
+ ${alpn.version}
+
+
+
+
+
+
+
+ alpn-when-jdk8_05
+
+ 1.8.0_05
+
+
+ 8.1.0.v20141016
+
+
+
+ alpn-when-jdk8_11
+
+ 1.8.0_11
+
+
+ 8.1.0.v20141016
+
+
+
+ alpn-when-jdk8_20
+
+ 1.8.0_20
+
+
+ 8.1.0.v20141016
+
+
+
+ alpn-when-jdk8_25
+
+ 1.8.0_25
+
+
+ 8.1.2.v20141202
+
+
+
+ alpn-when-jdk8_31
+
+ 1.8.0_31
+
+
+ 8.1.3.v20150130
+
+
+
+ alpn-when-jdk8_40
+
+ 1.8.0_40
+
+
+ 8.1.3.v20150130
+
+
+
+ alpn-when-jdk8_45
+
+ 1.8.0_45
+
+
+ 8.1.3.v20150130
+
+
+
+ alpn-when-jdk8_51
+
+ 1.8.0_51
+
+
+ 8.1.4.v20150727
+
+
+
+ alpn-when-jdk8_60
+
+ 1.8.0_60
+
+
+ 8.1.5.v20150921
+
+
+
+ alpn-when-jdk8_65
+
+ 1.8.0_65
+
+
+ 8.1.6.v20151105
+
+
+
+ alpn-when-jdk8_66
+
+ 1.8.0_66
+
+
+ 8.1.6.v20151105
+
+
+
+ alpn-when-jdk8_71
+
+ 1.8.0_71
+
+
+ 8.1.7.v20160121
+
+
+
+ alpn-when-jdk8_72
+
+ 1.8.0_72
+
+
+ 8.1.7.v20160121
+
+
+
+ alpn-when-jdk8_73
+
+ 1.8.0_73
+
+
+ 8.1.7.v20160121
+
+
+
+ alpn-when-jdk8_74
+
+ 1.8.0_74
+
+
+ 8.1.7.v20160121
+
+
+
+ alpn-when-jdk8_77
+
+ 1.8.0_77
+
+
+ 8.1.7.v20160121
+
+
+
+ alpn-when-jdk8_91
+
+ 1.8.0_91
+
+
+ 8.1.7.v20160121
+
+
+
+ alpn-when-jdk8_92
+
+ 1.8.0_92
+
+
+ 8.1.8.v20160420
+
+
+
+ alpn-when-jdk8_101
+
+ 1.8.0_101
+
+
+ 8.1.8.v20160420
+
+
+
+ alpn-when-jdk8_102
+
+ 1.8.0_102
+
+
+ 8.1.9.v20160720
+
+
+
+ alpn-when-jdk8_111
+
+ 1.8.0_111
+
+
+ 8.1.9.v20160720
+
+
+
+ alpn-when-jdk8_112
+
+ 1.8.0_112
+
+
+ 8.1.9.v20160720
+
+
+
+ alpn-when-jdk8_121
+
+ 1.8.0_121
+
+
+ 8.1.11.v20170118
+
+
+
+ alpn-when-jdk8_131
+
+ 1.8.0_131
+
+
+ 8.1.11.v20170118
+
+
+
+ alpn-when-jdk8_141
+
+ 1.8.0_141
+
+
+ 8.1.11.v20170118
+
+
+
+ alpn-when-jdk8_144
+
+ 1.8.0_144
+
+
+ 8.1.11.v20170118
+
+
+
+ alpn-when-jdk8_151
+
+ 1.8.0_151
+
+
+ 8.1.11.v20170118
+
+
+
+ alpn-when-jdk8_152
+
+ 1.8.0_152
+
+
+ 8.1.11.v20170118
+
+
+
+ alpn-when-jdk8_161
+
+ 1.8.0_161
+
+
+ 8.1.12.v20180117
+
+
+
+ alpn-when-jdk8_162
+
+ 1.8.0_162
+
+
+ 8.1.12.v20180117
+
+
+
+ alpn-when-jdk8_171
+
+ 1.8.0_171
+
+
+ 8.1.12.v20180117
+
+
+
+ alpn-when-jdk8_172
+
+ 1.8.0_172
+
+
+ 8.1.12.v20180117
+
+
+
+ alpn-when-jdk8_181
+
+ 1.8.0_181
+
+
+ 8.1.12.v20180117
+
+
+
+ alpn-when-jdk8_191
+
+ 1.8.0_191
+
+
+ 8.1.13.v20181017
+
+
+
+ alpn-when-jdk8_192
+
+ 1.8.0_192
+
+
+ 8.1.13.v20181017
+
+
+
+ alpn-when-jdk8_201
+
+ 1.8.0_201
+
+
+ 8.1.13.v20181017
+
+
+
+ alpn-when-jdk8_202
+
+ 1.8.0_202
+
+
+ 8.1.13.v20181017
+
+
+
+ alpn-when-jdk8_211
+
+ 1.8.0_211
+
+
+ 8.1.13.v20181017
+
+
+
+ alpn-when-jdk8_212
+
+ 1.8.0_212
+
+
+ 8.1.13.v20181017
+
+
+
+ alpn-when-jdk8_221
+
+ 1.8.0_221
+
+
+ 8.1.13.v20181017
+
+
+
+ alpn-when-jdk8_222
+
+ 1.8.0_222
+
+
+ 8.1.13.v20181017
+
+
+
+ alpn-when-jdk8_231
+
+ 1.8.0_231
+
+
+ 8.1.13.v20181017
+
+
+
+ alpn-when-jdk8_232
+
+ 1.8.0_232
+
+
+ 8.1.13.v20181017
+
+
+
+ alpn-when-jdk8_241
+
+ 1.8.0_241
+
+
+ 8.1.13.v20181017
+
+
+
+ alpn-when-jdk8_242
+
+ 1.8.0_242
+
+
+ 8.1.13.v20181017
+
+
+
+
+ alpn-when-jdk8_251
+
+ 1.8.0_251
+
+
+
+
+
+
+ alpn-when-jdk8_252
+
+ 1.8.0_252
+
+
+
+
+
+
+
+
diff --git a/servlet/http2/src/main/java/org/javaee8/servlet/http2/Servlet.java b/servlet/http2/src/main/java/org/javaee8/servlet/http2/Servlet.java
new file mode 100644
index 00000000..f8cf4746
--- /dev/null
+++ b/servlet/http2/src/main/java/org/javaee8/servlet/http2/Servlet.java
@@ -0,0 +1,40 @@
+package org.javaee8.servlet.http2;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.PushBuilder;
+import javax.ws.rs.core.MediaType;
+
+@WebServlet("/test")
+public class Servlet extends HttpServlet {
+
+ private static final long serialVersionUID = -3439982021784932020L;
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ response.setContentType(MediaType.TEXT_HTML_TYPE.withCharset(UTF_8.name()).toString());
+ response.setStatus(200);
+
+ PushBuilder builder = request.newPushBuilder();
+
+ // If server push isn't supported, return that in the result.
+ if (builder == null) {
+ response.addHeader("protocol", "HTTP 1.1");
+ response.getWriter().append("