Initial Contribution
Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/examples/LICENSE.md b/examples/LICENSE.md
new file mode 100644
index 0000000..d5ad335
--- /dev/null
+++ b/examples/LICENSE.md
@@ -0,0 +1,29 @@
+
+ Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ - Neither the name of the Eclipse Foundation, Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/examples/assemblies/pom.xml b/examples/assemblies/pom.xml
new file mode 100644
index 0000000..527177c
--- /dev/null
+++ b/examples/assemblies/pom.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>assemblies</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-assemblies</name>
+
+ <description>Jersey examples shared assembly types.</description>
+
+</project>
diff --git a/examples/assemblies/src/main/resources/assemblies/glassfish-src-zip.xml b/examples/assemblies/src/main/resources/assemblies/glassfish-src-zip.xml
new file mode 100644
index 0000000..44bf0bf
--- /dev/null
+++ b/examples/assemblies/src/main/resources/assemblies/glassfish-src-zip.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<assembly>
+ <!--
+ An assembly that produces an example project source zip bundle with
+ the jersey dependencies in the original project pom.xml file transformed
+ to the "provided" scope.
+ The resulting zipped example project sources are thus able to build example
+ binaries that are suitable for a deployment on containers which host Jersey
+ runtime by default (e.g. GlassFish).
+ -->
+ <id>gf-project-src</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <fileSets>
+ <fileSet>
+ <directory>${project.basedir}</directory>
+ <outputDirectory>/</outputDirectory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ <excludes>
+ <exclude>pom.xml</exclude>
+ <exclude>**/target/**</exclude>
+ <exclude>**/*.iml</exclude>
+ </excludes>
+ </fileSet>
+ <fileSet>
+ <directory>${project.basedir}/target/gf-pom-file</directory>
+ <outputDirectory>/</outputDirectory>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/examples/assemblies/src/main/resources/assemblies/src-zip.xml b/examples/assemblies/src/main/resources/assemblies/src-zip.xml
new file mode 100644
index 0000000..6f99a65
--- /dev/null
+++ b/examples/assemblies/src/main/resources/assemblies/src-zip.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<assembly>
+ <!--
+ An assembly that produces an example project source zip bundle with
+ the jersey dependencies in the original project pom.xml file transformed
+ to the "provided" scope.
+ The resulting zipped example project sources are thus able to build example
+ binaries that are suitable for a deployment on containers which host Jersey
+ runtime by default (e.g. GlassFish).
+ -->
+ <id>project-src</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <fileSets>
+ <fileSet>
+ <directory>${project.basedir}</directory>
+ <outputDirectory>/</outputDirectory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ <excludes>
+ <exclude>**/target/**</exclude>
+ <exclude>**/*.iml</exclude>
+ <exclude>**/weblogic.xml</exclude>
+ <exclude>**/glassfish-web.xml</exclude>
+ <exclude>**/sun-web.xml</exclude>
+ </excludes>
+ </fileSet>
+ </fileSets>
+</assembly>
+
diff --git a/examples/assemblies/src/main/resources/assemblies/weblogic-src-zip.xml b/examples/assemblies/src/main/resources/assemblies/weblogic-src-zip.xml
new file mode 100644
index 0000000..3aafa40
--- /dev/null
+++ b/examples/assemblies/src/main/resources/assemblies/weblogic-src-zip.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<assembly>
+ <!--
+ An assembly that produces an example project source zip bundle with
+ the jersey dependencies in the original project pom.xml file transformed
+ to the "provided" scope.
+ The resulting zipped example project sources are thus able to build example
+ binaries that are suitable for a deployment on containers which host Jersey
+ runtime by default (e.g. GlassFish).
+ -->
+ <id>wls-project-src</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <fileSets>
+ <fileSet>
+ <directory>${project.basedir}</directory>
+ <outputDirectory>/</outputDirectory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ <excludes>
+ <exclude>pom.xml</exclude>
+ <exclude>**/WEB-INF/web.xml</exclude>
+ <exclude>**/WEB-INF/weblogic.xml</exclude>
+ <exclude>**/target/**</exclude>
+ <exclude>**/*.iml</exclude>
+ <exclude>**/glassfish-web.xml</exclude>
+ <exclude>**/sun-web.xml</exclude>
+ </excludes>
+ </fileSet>
+ <fileSet>
+ <directory>${project.basedir}/target/wls-pom-file</directory>
+ <outputDirectory>/</outputDirectory>
+ </fileSet>
+ <fileSet>
+ <!-- will copy everything from /examples/etc/wls/main/webapp/WEB-INF -->
+ <!-- currently the directory is empty -->
+ <directory>${project.basedir}/target/wls-web-xml-file/src/main/webapp/WEB-INF</directory>
+ <outputDirectory>/src/main/webapp/WEB-INF</outputDirectory>
+ </fileSet>
+ <fileSet>
+ <directory>${project.basedir}/../etc/wls</directory>
+ <outputDirectory>/</outputDirectory>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/examples/assemblies/src/main/resources/assemblies/weblogic1213-src-zip.xml b/examples/assemblies/src/main/resources/assemblies/weblogic1213-src-zip.xml
new file mode 100644
index 0000000..7adfa57
--- /dev/null
+++ b/examples/assemblies/src/main/resources/assemblies/weblogic1213-src-zip.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<assembly>
+ <!--
+ An assembly that produces an example project source zip bundle with
+ the jersey dependencies in the original project pom.xml file transformed
+ to the "provided" scope.
+ The resulting zipped example project sources are thus able to build example
+ binaries that are suitable for a deployment on containers which host Jersey
+ runtime by default (e.g. GlassFish).
+ -->
+ <id>wls1213-project-src</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <fileSets>
+ <fileSet>
+ <directory>${project.basedir}</directory>
+ <outputDirectory>/</outputDirectory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ <excludes>
+ <exclude>pom.xml</exclude>
+ <exclude>**/WEB-INF/web.xml</exclude>
+ <exclude>**/target/**</exclude>
+ <exclude>**/*.iml</exclude>
+ <exclude>**/glassfish-web.xml</exclude>
+ <exclude>**/sun-web.xml</exclude>
+ </excludes>
+ </fileSet>
+ <fileSet>
+ <directory>${project.basedir}/target/wls1213-pom-file</directory>
+ <outputDirectory>/</outputDirectory>
+ </fileSet>
+ <fileSet>
+ <directory>${project.basedir}/target/wls1213-web-xml-file/src/main/webapp/WEB-INF</directory>
+ <outputDirectory>/src/main/webapp/WEB-INF</outputDirectory>
+ </fileSet>
+ <fileSet>
+ <directory>${project.basedir}/../etc/wls1213</directory>
+ <outputDirectory>/</outputDirectory>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/examples/bookmark-em/README.MD b/examples/bookmark-em/README.MD
new file mode 100644
index 0000000..ab9b57d
--- /dev/null
+++ b/examples/bookmark-em/README.MD
@@ -0,0 +1,60 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Bookmark EM Example
+===================
+
+This example demonstrates how to use JPA in the backend. The example is
+based on bookmark example from the [RESTful Web
+Services](http://www.oreilly.com/catalog/9780596529260/) book, which was
+inspired by the [http://del.icio.us/](http://del.icio.us/v1) web
+service.
+
+A bookmark web application is presented that is capable of maintaining
+users and their bookmarks.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Notes
+---------------------------------------- | ------------------- | ----------------------------------------------------- | --------------------------------------------------------
+**_/users_** | UsersResource | GET | Returns a list of users.
+**_/users/{userid}_** | UserResource | GET, PUT (used also for creating new users), DELETE | Returns user details
+**_/users/{userid}/bookmarks_** | BookmarksResource | GET, POST | Returns a list of bookmarks for actual user resource.
+**_/users/{userid}/bookmarks/{bmid}_** | BookmarkResource | GET, PUT, DELETE | Returns bookmark uri and a long and short description.
+
+Running the Example
+-------------------
+
+Bookmark example runs on Glassfish 3.1 application server
+([https://javaee.github.io/glassfish/](https://javaee.github.io/glassfish/)) and needs a running JavaDB
+(<http://www.oracle.com/technetwork/java/javadb/overview/index.html>)
+instance for underlying data (it comes along with GlassFish 3.1).
+
+Presuming, you have installed Glassfish 3.1. `AS_HOME` variable should
+point to your glassfish installation directory.
+
+#### Building And Starting The Bookmark Service
+
+You then build and run the example by
+
+ mvn package
+ $AS_HOME/bin/asadmin start-domain
+ $AS_HOME/bin/asadmin start-database
+ $AS_HOME/bin/asadmin deploy target/bookmark-em.war
+
+#### Test Client Running
+
+ mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.external.ExternalTestContainerFactory -Djersey.config.test.logging.enable=true -Djersey.config.test.logging.dumpEntity=true -Djersey.config.test.container.port=8080 -Ptest
+
+Links
+-----
+
+<http://java.net/projects/bpcatalog/sources/svn/content/trunk/ee5/docs/persistence/webonlyapp.html?raw=true>
diff --git a/examples/bookmark-em/pom.xml b/examples/bookmark-em/pom.xml
new file mode 100644
index 0000000..12927d3
--- /dev/null
+++ b/examples/bookmark-em/pom.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>bookmark-em</artifactId>
+ <name>jersey-examples-bookmark-em</name>
+ <packaging>war</packaging>
+
+ <description>Jersey Bookmark example using EntityManager.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jettison</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.persistence</groupId>
+ <artifactId>persistence-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.ejb</groupId>
+ <artifactId>javax.ejb-api</artifactId>
+ <version>${ejb.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.transaction</groupId>
+ <artifactId>jta</artifactId>
+ <version>1.1</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Run the application using "mvn embedded-glassfish:run" -->
+ <plugin>
+ <groupId>org.glassfish.embedded</groupId>
+ <artifactId>maven-embedded-glassfish-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>run-external-tests</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <jersey.config.test.container.factory>${external.container.factory}</jersey.config.test.container.factory>
+ <jersey.config.test.container.port>${external.container.port}</jersey.config.test.container.port>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <properties>
+ <!-- External test container configuration is done via properties to allow overriding via command line. -->
+ <external.container.factory>org.glassfish.jersey.test.external.ExternalTestContainerFactory</external.container.factory>
+ <external.container.port>8080</external.container.port>
+ <maven.test.skip>false</maven.test.skip>
+ </properties>
+ </profile>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <properties>
+ <maven.test.skip>true</maven.test.skip>
+ </properties>
+
+</project>
diff --git a/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/MyApplication.java b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/MyApplication.java
new file mode 100644
index 0000000..2cd0138
--- /dev/null
+++ b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/MyApplication.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark_em;
+
+import javax.ws.rs.ApplicationPath;
+
+import org.glassfish.jersey.examples.bookmark_em.resource.UsersResource;
+import org.glassfish.jersey.jettison.JettisonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * @author Michal Gajdos
+ */
+@ApplicationPath("/")
+public class MyApplication extends ResourceConfig {
+
+ public MyApplication() {
+ registerClasses(UsersResource.class);
+ register(new JettisonFeature());
+ }
+}
diff --git a/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/entity/BookmarkEntity.java b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/entity/BookmarkEntity.java
new file mode 100644
index 0000000..e4d4d77
--- /dev/null
+++ b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/entity/BookmarkEntity.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark_em.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.annotation.ManagedBean;
+import javax.persistence.Column;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+/**
+ * Entity class BookmarkEntity.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Entity
+@Table(name = "BOOKMARKS")
+@NamedQueries({
+ @NamedQuery(name = "BookmarkEntity.findByUserid",
+ query = "SELECT b FROM BookmarkEntity b WHERE b.bookmarkEntityPK.userid = :userid"),
+ @NamedQuery(name = "BookmarkEntity.findByBmid",
+ query = "SELECT b FROM BookmarkEntity b WHERE b.bookmarkEntityPK.bmid = :bmid"),
+ @NamedQuery(name = "BookmarkEntity.findByUri", query = "SELECT b FROM BookmarkEntity b WHERE b.uri = :uri"),
+ @NamedQuery(name = "BookmarkEntity.findByUpdated", query = "SELECT b FROM BookmarkEntity b WHERE b.updated = :updated"),
+ @NamedQuery(name = "BookmarkEntity.findByLdesc", query = "SELECT b FROM BookmarkEntity b WHERE b.ldesc = :ldesc"),
+ @NamedQuery(name = "BookmarkEntity.findBySdesc", query = "SELECT b FROM BookmarkEntity b WHERE b.sdesc = :sdesc")
+})
+@ManagedBean
+@SuppressWarnings("UnusedDeclaration")
+public class BookmarkEntity implements Serializable {
+
+ /**
+ * EmbeddedId primary key field
+ */
+ @EmbeddedId
+ protected BookmarkEntityPK bookmarkEntityPK;
+
+ @Column(name = "URI", nullable = false)
+ private String uri;
+
+ @Column(name = "UPDATED")
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date updated;
+
+ @Column(name = "LDESC")
+ private String ldesc;
+
+ @Column(name = "SDESC")
+ private String sdesc;
+
+ @JoinColumn(name = "USERID", referencedColumnName = "USERID", insertable = false, updatable = false)
+ @ManyToOne
+ private UserEntity userEntity;
+
+ /**
+ * Creates a new instance of BookmarkEntity
+ */
+ public BookmarkEntity() {
+ }
+
+ /**
+ * Creates a new instance of BookmarkEntity with the specified values.
+ *
+ * @param bookmarkEntityPK the bookmarkEntityPK of the BookmarkEntity
+ */
+ public BookmarkEntity(BookmarkEntityPK bookmarkEntityPK) {
+ this.bookmarkEntityPK = bookmarkEntityPK;
+ }
+
+ /**
+ * Creates a new instance of BookmarkEntity with the specified values.
+ *
+ * @param bookmarkEntityPK the bookmarkEntityPK of the BookmarkEntity
+ * @param uri the uri of the BookmarkEntity
+ */
+ public BookmarkEntity(BookmarkEntityPK bookmarkEntityPK, String uri) {
+ this.bookmarkEntityPK = bookmarkEntityPK;
+ this.uri = uri;
+ }
+
+ /**
+ * Creates a new instance of BookmarkEntityPK with the specified values.
+ *
+ * @param bmid the bmid of the BookmarkEntityPK
+ * @param userid the userid of the BookmarkEntityPK
+ */
+ public BookmarkEntity(String bmid, String userid) {
+ this.bookmarkEntityPK = new BookmarkEntityPK(bmid, userid);
+ }
+
+ /**
+ * Gets the bookmarkEntityPK of this BookmarkEntity.
+ *
+ * @return the bookmarkEntityPK
+ */
+ public BookmarkEntityPK getBookmarkEntityPK() {
+ return this.bookmarkEntityPK;
+ }
+
+ /**
+ * Sets the bookmarkEntityPK of this BookmarkEntity to the specified value.
+ *
+ * @param bookmarkEntityPK the new bookmarkEntityPK
+ */
+ public void setBookmarkEntityPK(BookmarkEntityPK bookmarkEntityPK) {
+ this.bookmarkEntityPK = bookmarkEntityPK;
+ }
+
+ /**
+ * Gets the uri of this BookmarkEntity.
+ *
+ * @return the uri
+ */
+ public String getUri() {
+ return this.uri;
+ }
+
+ /**
+ * Sets the uri of this BookmarkEntity to the specified value.
+ *
+ * @param uri the new uri
+ */
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ /**
+ * Gets the updated of this BookmarkEntity.
+ *
+ * @return the updated
+ */
+ public Date getUpdated() {
+ return this.updated;
+ }
+
+ /**
+ * Sets the updated of this BookmarkEntity to the specified value.
+ *
+ * @param updated the new updated
+ */
+ public void setUpdated(Date updated) {
+ this.updated = updated;
+ }
+
+ /**
+ * Gets the ldesc of this BookmarkEntity.
+ *
+ * @return the ldesc
+ */
+ public String getLdesc() {
+ return this.ldesc;
+ }
+
+ /**
+ * Sets the ldesc of this BookmarkEntity to the specified value.
+ *
+ * @param ldesc the new ldesc
+ */
+ public void setLdesc(String ldesc) {
+ this.ldesc = ldesc;
+ }
+
+ /**
+ * Gets the sdesc of this BookmarkEntity.
+ *
+ * @return the sdesc
+ */
+ public String getSdesc() {
+ return this.sdesc;
+ }
+
+ /**
+ * Sets the sdesc of this BookmarkEntity to the specified value.
+ *
+ * @param sdesc the new sdesc
+ */
+ public void setSdesc(String sdesc) {
+ this.sdesc = sdesc;
+ }
+
+ /**
+ * Gets the userEntity of this BookmarkEntity.
+ *
+ * @return the userEntity
+ */
+ public UserEntity getUserEntity() {
+ return this.userEntity;
+ }
+
+ /**
+ * Sets the userEntity of this BookmarkEntity to the specified value.
+ *
+ * @param userEntity the new userEntity
+ */
+ public void setUserEntity(UserEntity userEntity) {
+ this.userEntity = userEntity;
+ }
+
+ /**
+ * Returns a hash code value for the object. This implementation computes
+ * a hash code value based on the id fields in this object.
+ *
+ * @return a hash code value for this object.
+ */
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ hash += (this.bookmarkEntityPK != null ? this.bookmarkEntityPK.hashCode() : 0);
+ return hash;
+ }
+
+ /**
+ * Determines whether another object is equal to this BookmarkEntity. The result is
+ * <code>true</code> if and only if the argument is not null and is a BookmarkEntity object that
+ * has the same id field values as this object.
+ *
+ * @param object the reference object with which to compare
+ * @return <code>true</code> if this object is the same as the argument;
+ * <code>false</code> otherwise.
+ */
+ @Override
+ public boolean equals(Object object) {
+ // TODO: Warning - this method won't work in the case the id fields are not set
+ if (!(object instanceof BookmarkEntity)) {
+ return false;
+ }
+ BookmarkEntity other = (BookmarkEntity) object;
+ return !(this.bookmarkEntityPK != other.bookmarkEntityPK && (this.bookmarkEntityPK == null || !this.bookmarkEntityPK
+ .equals(other.bookmarkEntityPK)));
+ }
+
+ /**
+ * Returns a string representation of the object. This implementation constructs
+ * that representation based on the id fields.
+ *
+ * @return a string representation of the object.
+ */
+ @Override
+ public String toString() {
+ return "BookmarkEntity{"
+ + "bookmarkEntityPK=" + bookmarkEntityPK
+ + '}';
+ }
+}
diff --git a/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/entity/BookmarkEntityPK.java b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/entity/BookmarkEntityPK.java
new file mode 100644
index 0000000..ff2c36c
--- /dev/null
+++ b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/entity/BookmarkEntityPK.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark_em.entity;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+/**
+ * Primary Key class BookmarkEntityPK for entity class BookmarkEntity.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@SuppressWarnings("UnusedDeclaration")
+@Embeddable
+public class BookmarkEntityPK implements Serializable {
+
+ @Column(name = "USERID", nullable = false)
+ private String userid;
+
+ @Column(name = "BMID", nullable = false)
+ private String bmid;
+
+ /**
+ * Creates a new instance of BookmarkEntityPK
+ */
+ public BookmarkEntityPK() {
+ }
+
+ /**
+ * Creates a new instance of BookmarkEntityPK with the specified values.
+ *
+ * @param bmid the bmid of the BookmarkEntityPK
+ * @param userid the userid of the BookmarkEntityPK
+ */
+ public BookmarkEntityPK(String bmid, String userid) {
+ this.bmid = bmid;
+ this.userid = userid;
+ }
+
+ /**
+ * Gets the userid of this BookmarkEntityPK.
+ *
+ * @return the userid
+ */
+ public String getUserid() {
+ return this.userid;
+ }
+
+ /**
+ * Sets the userid of this BookmarkEntityPK to the specified value.
+ *
+ * @param userid the new userid
+ */
+ public void setUserid(String userid) {
+ this.userid = userid;
+ }
+
+ /**
+ * Gets the bmid of this BookmarkEntityPK.
+ *
+ * @return the bmid
+ */
+ public String getBmid() {
+ return this.bmid;
+ }
+
+ /**
+ * Sets the bmid of this BookmarkEntityPK to the specified value.
+ *
+ * @param bmid the new bmid
+ */
+ public void setBmid(String bmid) {
+ this.bmid = bmid;
+ }
+
+ /**
+ * Returns a hash code value for the object. This implementation computes
+ * a hash code value based on the id fields in this object.
+ *
+ * @return a hash code value for this object.
+ */
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ hash += (this.bmid != null ? this.bmid.hashCode() : 0);
+ hash += (this.userid != null ? this.userid.hashCode() : 0);
+ return hash;
+ }
+
+ /**
+ * Determines whether another object is equal to this BookmarkEntityPK. The result is
+ * <code>true</code> if and only if the argument is not null and is a BookmarkEntityPK object that
+ * has the same id field values as this object.
+ *
+ * @param object the reference object with which to compare
+ * @return <code>true</code> if this object is the same as the argument;
+ * <code>false</code> otherwise.
+ */
+ @SuppressWarnings("StringEquality")
+ @Override
+ public boolean equals(Object object) {
+ // TODO: Warning - this method won't work in the case the id fields are not set
+ if (!(object instanceof BookmarkEntityPK)) {
+ return false;
+ }
+
+ BookmarkEntityPK other = (BookmarkEntityPK) object;
+ if (this.bmid != other.bmid && (this.bmid == null || !this.bmid.equals(other.bmid))) {
+ return false;
+ }
+ if (this.userid != other.userid && (this.userid == null || !this.userid.equals(other.userid))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns a string representation of the object. This implementation constructs
+ * that representation based on the id fields.
+ *
+ * @return a string representation of the object.
+ */
+ @Override
+ public String toString() {
+ return "BookmarkEntityPK{"
+ + "userid='" + userid + '\''
+ + ", bmid='" + bmid + '\''
+ + '}';
+ }
+}
diff --git a/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/entity/UserEntity.java b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/entity/UserEntity.java
new file mode 100644
index 0000000..06a2e69
--- /dev/null
+++ b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/entity/UserEntity.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark_em.entity;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+/**
+ * Entity class UserEntity.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Entity
+@Table(name = "USERS")
+@NamedQueries({
+ @NamedQuery(name = "UserEntity.findByUserid", query = "SELECT u FROM UserEntity u WHERE u.userid = :userid"),
+ @NamedQuery(name = "UserEntity.findByPassword", query = "SELECT u FROM UserEntity u WHERE u.password = :password"),
+ @NamedQuery(name = "UserEntity.findByUsername", query = "SELECT u FROM UserEntity u WHERE u.username = :username"),
+ @NamedQuery(name = "UserEntity.findByEmail", query = "SELECT u FROM UserEntity u WHERE u.email = :email")
+})
+@SuppressWarnings("UnusedDeclaration")
+public class UserEntity implements Serializable {
+
+ @Id
+ @Column(name = "USERID", nullable = false)
+ private String userid;
+
+ @Column(name = "PASSWORD", nullable = false)
+ private String password;
+
+ @Column(name = "USERNAME")
+ private String username;
+
+ @Column(name = "EMAIL")
+ private String email;
+
+ @OneToMany(cascade = CascadeType.ALL, mappedBy = "userEntity")
+ private Collection<BookmarkEntity> bookmarkEntityCollection;
+
+ /**
+ * Creates a new instance of UserEntity
+ */
+ public UserEntity() {
+ }
+
+ /**
+ * Creates a new instance of UserEntity with the specified values.
+ *
+ * @param userid the userid of the UserEntity
+ */
+ public UserEntity(String userid) {
+ this.userid = userid;
+ }
+
+ /**
+ * Creates a new instance of UserEntity with the specified values.
+ *
+ * @param userid the userid of the UserEntity
+ * @param password the password of the UserEntity
+ */
+ public UserEntity(String userid, String password) {
+ this.userid = userid;
+ this.password = password;
+ }
+
+ /**
+ * Gets the userid of this UserEntity.
+ *
+ * @return the userid
+ */
+ public String getUserid() {
+ return this.userid;
+ }
+
+ /**
+ * Sets the userid of this UserEntity to the specified value.
+ *
+ * @param userid the new userid
+ */
+ public void setUserid(String userid) {
+ this.userid = userid;
+ }
+
+ /**
+ * Gets the password of this UserEntity.
+ *
+ * @return the password
+ */
+ public String getPassword() {
+ return this.password;
+ }
+
+ /**
+ * Sets the password of this UserEntity to the specified value.
+ *
+ * @param password the new password
+ */
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ /**
+ * Gets the username of this UserEntity.
+ *
+ * @return the username
+ */
+ public String getUsername() {
+ return this.username;
+ }
+
+ /**
+ * Sets the username of this UserEntity to the specified value.
+ *
+ * @param username the new username
+ */
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ /**
+ * Gets the email of this UserEntity.
+ *
+ * @return the email
+ */
+ public String getEmail() {
+ return this.email;
+ }
+
+ /**
+ * Sets the email of this UserEntity to the specified value.
+ *
+ * @param email the new email
+ */
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ /**
+ * Gets the bookmarkEntityCollection of this UserEntity.
+ *
+ * @return the bookmarkEntityCollection
+ */
+ public Collection<BookmarkEntity> getBookmarkEntityCollection() {
+ return this.bookmarkEntityCollection;
+ }
+
+ /**
+ * Sets the bookmarkEntityCollection of this UserEntity to the specified value.
+ *
+ * @param bookmarkEntityCollection the new bookmarkEntityCollection
+ */
+ public void setBookmarkEntityCollection(Collection<BookmarkEntity> bookmarkEntityCollection) {
+ this.bookmarkEntityCollection = bookmarkEntityCollection;
+ }
+
+ /**
+ * Returns a hash code value for the object. This implementation computes
+ * a hash code value based on the id fields in this object.
+ *
+ * @return a hash code value for this object.
+ */
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ hash += (this.userid != null ? this.userid.hashCode() : 0);
+ return hash;
+ }
+
+ /**
+ * Determines whether another object is equal to this UserEntity. The result is
+ * <code>true</code> if and only if the argument is not null and is a UserEntity object that
+ * has the same id field values as this object.
+ *
+ * @param object the reference object with which to compare
+ * @return <code>true</code> if this object is the same as the argument;
+ * <code>false</code> otherwise.
+ */
+ @SuppressWarnings("StringEquality")
+ @Override
+ public boolean equals(Object object) {
+ // TODO: Warning - this method won't work in the case the id fields are not set
+ if (!(object instanceof UserEntity)) {
+ return false;
+ }
+ UserEntity other = (UserEntity) object;
+ return !(this.userid != other.userid && (this.userid == null || !this.userid.equals(other.userid)));
+ }
+
+ /**
+ * Returns a string representation of the object. This implementation constructs
+ * that representation based on the id fields.
+ *
+ * @return a string representation of the object.
+ */
+ @Override
+ public String toString() {
+ return "UserEntity{"
+ + "userid='" + userid + '\''
+ + '}';
+ }
+}
diff --git a/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/exception/ExtendedNotFoundException.java b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/exception/ExtendedNotFoundException.java
new file mode 100644
index 0000000..8a2926e
--- /dev/null
+++ b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/exception/ExtendedNotFoundException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark_em.exception;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author Michal Gajdos
+ */
+public class ExtendedNotFoundException extends NotFoundException {
+
+ public ExtendedNotFoundException(final String message) {
+ super(Response.status(Response.Status.NOT_FOUND).entity(message).build());
+ }
+}
diff --git a/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/resource/BookmarkResource.java b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/resource/BookmarkResource.java
new file mode 100644
index 0000000..3f1bc40
--- /dev/null
+++ b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/resource/BookmarkResource.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark_em.resource;
+
+import java.util.Date;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.UriInfo;
+
+import javax.persistence.EntityManager;
+import javax.transaction.UserTransaction;
+
+import org.glassfish.jersey.examples.bookmark_em.entity.BookmarkEntity;
+import org.glassfish.jersey.examples.bookmark_em.entity.BookmarkEntityPK;
+import org.glassfish.jersey.examples.bookmark_em.entity.UserEntity;
+import org.glassfish.jersey.examples.bookmark_em.exception.ExtendedNotFoundException;
+import org.glassfish.jersey.examples.bookmark_em.util.tx.TransactionManager;
+import org.glassfish.jersey.examples.bookmark_em.util.tx.Transactional;
+
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+public class BookmarkResource {
+
+ UriInfo uriInfo; // actual uri info provided by parent resource
+ EntityManager em; // entity manager provided by parent resource
+ UserTransaction utx; //user transaction provided by parent resource
+
+ BookmarkEntity bookmarkEntity;
+
+ /**
+ * Creates a new instance of UserResource
+ */
+ public BookmarkResource(UriInfo uriInfo, EntityManager em, UserTransaction utx, UserEntity userEntity, String bmid) {
+ this.uriInfo = uriInfo;
+ this.em = em;
+ this.utx = utx;
+
+ bookmarkEntity = em.find(BookmarkEntity.class, new BookmarkEntityPK(bmid, userEntity.getUserid()));
+ if (null == bookmarkEntity) {
+ throw new ExtendedNotFoundException("bookmark with userid="
+ + userEntity.getUserid() + " and bmid="
+ + bmid + " not found\n");
+ }
+ bookmarkEntity.setUserEntity(userEntity);
+ }
+
+ @GET
+ @Produces("application/json")
+ public JSONObject getBookmark() {
+ return asJson();
+ }
+
+ @PUT
+ @Consumes("application/json")
+ public void putBookmark(JSONObject jsonEntity) throws JSONException {
+
+ bookmarkEntity.setLdesc(jsonEntity.getString("ldesc"));
+ bookmarkEntity.setSdesc(jsonEntity.getString("sdesc"));
+ bookmarkEntity.setUpdated(new Date());
+
+ TransactionManager.manage(utx, new Transactional(em) {
+ public void transact() {
+ em.merge(bookmarkEntity);
+ }
+ });
+ }
+
+ @DELETE
+ public void deleteBookmark() {
+ TransactionManager.manage(utx, new Transactional(em) {
+ public void transact() {
+ em.persist(bookmarkEntity);
+ UserEntity userEntity = bookmarkEntity.getUserEntity();
+ userEntity.getBookmarkEntityCollection().remove(bookmarkEntity);
+ em.merge(userEntity);
+ em.remove(bookmarkEntity);
+ }
+ });
+ }
+
+ public String asString() {
+ return toString();
+ }
+
+ public JSONObject asJson() {
+ try {
+ return new JSONObject()
+ .put("userid", bookmarkEntity.getBookmarkEntityPK().getUserid())
+ .put("sdesc", bookmarkEntity.getSdesc())
+ .put("ldesc", bookmarkEntity.getLdesc())
+ .put("uri", bookmarkEntity.getUri());
+ } catch (JSONException je) {
+ return null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return bookmarkEntity.getBookmarkEntityPK().getUserid();
+ }
+}
diff --git a/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/resource/BookmarksResource.java b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/resource/BookmarksResource.java
new file mode 100644
index 0000000..7b566e3
--- /dev/null
+++ b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/resource/BookmarksResource.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark_em.resource;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.Date;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import javax.persistence.EntityManager;
+import javax.transaction.UserTransaction;
+
+import org.glassfish.jersey.examples.bookmark_em.entity.BookmarkEntity;
+import org.glassfish.jersey.examples.bookmark_em.util.tx.TransactionManager;
+import org.glassfish.jersey.examples.bookmark_em.util.tx.Transactional;
+
+import org.codehaus.jettison.json.JSONArray;
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+public class BookmarksResource {
+
+ UriInfo uriInfo; // actual uri info
+ EntityManager em; // entity manager provided by parent resource
+ UserTransaction utx; // user transaction provided by parent resource
+
+ UserResource userResource; // parent user resource
+
+ /**
+ * Creates a new instance of BookmarksResource
+ */
+ public BookmarksResource(UriInfo uriInfo, EntityManager em, UserTransaction utx, UserResource userResource) {
+ this.uriInfo = uriInfo;
+ this.em = em;
+ this.utx = utx;
+ this.userResource = userResource;
+ }
+
+ public Collection<BookmarkEntity> getBookmarks() {
+ return userResource.getUserEntity().getBookmarkEntityCollection();
+ }
+
+ @Path("{bmid: .+}")
+ public BookmarkResource getBookmark(@PathParam("bmid") String bmid) {
+ return new BookmarkResource(uriInfo, em, utx, userResource.getUserEntity(), bmid);
+ }
+
+ @GET
+ @Produces("application/json")
+ public JSONArray getBookmarksAsJsonArray() {
+ JSONArray uriArray = new JSONArray();
+ for (BookmarkEntity bookmarkEntity : getBookmarks()) {
+ UriBuilder ub = uriInfo.getAbsolutePathBuilder();
+ URI bookmarkUri = ub
+ .path(bookmarkEntity.getBookmarkEntityPK().getBmid())
+ .build();
+ uriArray.put(bookmarkUri.toASCIIString());
+ }
+ return uriArray;
+ }
+
+ @POST
+ @Consumes("application/json")
+ public Response postForm(JSONObject bookmark) throws JSONException {
+ final BookmarkEntity bookmarkEntity = new BookmarkEntity(getBookmarkId(bookmark.getString("uri")),
+ userResource.getUserEntity().getUserid());
+
+ bookmarkEntity.setUri(bookmark.getString("uri"));
+ bookmarkEntity.setUpdated(new Date());
+ bookmarkEntity.setSdesc(bookmark.getString("sdesc"));
+ bookmarkEntity.setLdesc(bookmark.getString("ldesc"));
+ userResource.getUserEntity().getBookmarkEntityCollection().add(bookmarkEntity);
+
+ TransactionManager.manage(utx, new Transactional(em) {
+ public void transact() {
+ em.merge(userResource.getUserEntity());
+ }
+ });
+
+ URI bookmarkUri = uriInfo.getAbsolutePathBuilder()
+ .path(bookmarkEntity.getBookmarkEntityPK().getBmid())
+ .build();
+ return Response.created(bookmarkUri).build();
+ }
+
+ private String getBookmarkId(String uri) {
+ return uri;
+ }
+}
diff --git a/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/resource/UserResource.java b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/resource/UserResource.java
new file mode 100644
index 0000000..3ffb842
--- /dev/null
+++ b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/resource/UserResource.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark_em.resource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import javax.persistence.EntityManager;
+import javax.transaction.UserTransaction;
+
+import org.glassfish.jersey.examples.bookmark_em.entity.UserEntity;
+import org.glassfish.jersey.examples.bookmark_em.exception.ExtendedNotFoundException;
+import org.glassfish.jersey.examples.bookmark_em.util.tx.TransactionManager;
+import org.glassfish.jersey.examples.bookmark_em.util.tx.Transactional;
+
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+public class UserResource {
+
+ String userid; // userid from url
+ UserEntity userEntity; // appropriate jpa user entity
+
+ UriInfo uriInfo; // actual uri info provided by parent resource
+ EntityManager em; // entity manager provided by parent resource
+ UserTransaction utx; // user transaction provided by parent resource
+
+ /**
+ * Creates a new instance of UserResource
+ */
+ public UserResource(UriInfo uriInfo, EntityManager em, UserTransaction utx, String userid) {
+ this.uriInfo = uriInfo;
+ this.userid = userid;
+ this.em = em;
+ this.utx = utx;
+ userEntity = em.find(UserEntity.class, userid);
+ }
+
+ @Path("bookmarks/")
+ public BookmarksResource getBookmarksResource() {
+ if (null == userEntity) {
+ throw new ExtendedNotFoundException("userid " + userid + " does not exist!");
+ }
+ return new BookmarksResource(uriInfo, em, utx, this);
+ }
+
+ @GET
+ @Produces("application/json")
+ public JSONObject getUser() throws JSONException {
+ if (null == userEntity) {
+ throw new ExtendedNotFoundException("userid " + userid + "does not exist!");
+ }
+ return new JSONObject()
+ .put("userid", userEntity.getUserid())
+ .put("username", userEntity.getUsername())
+ .put("email", userEntity.getEmail())
+ .put("password", userEntity.getPassword())
+ .put("bookmarks", uriInfo.getAbsolutePathBuilder().path("bookmarks").build());
+ }
+
+ @PUT
+ @Consumes("application/json")
+ public Response putUser(JSONObject jsonEntity) throws JSONException {
+
+ String jsonUserid = jsonEntity.getString("userid");
+
+ if ((null != jsonUserid) && !jsonUserid.equals(userid)) {
+ return Response.status(409).entity("userids differ!\n").build();
+ }
+
+ final boolean newRecord = (null == userEntity); // insert or update ?
+
+ if (newRecord) { // new user record to be inserted
+ userEntity = new UserEntity();
+ userEntity.setUserid(userid);
+ }
+ userEntity.setUsername(jsonEntity.getString("username"));
+ userEntity.setEmail(jsonEntity.getString("email"));
+ userEntity.setPassword(jsonEntity.getString("password"));
+
+ if (newRecord) {
+ TransactionManager.manage(utx, new Transactional(em) {
+ public void transact() {
+ em.joinTransaction();
+ em.persist(userEntity);
+ }
+ });
+ return Response.created(uriInfo.getAbsolutePath()).build();
+ } else {
+ TransactionManager.manage(utx, new Transactional(em) {
+ public void transact() {
+ em.merge(userEntity);
+ }
+ });
+ return Response.noContent().build();
+ }
+ }
+
+ @DELETE
+ public void deleteUser() {
+ if (null == userEntity) {
+ throw new ExtendedNotFoundException("userid " + userid + "does not exist!");
+ }
+
+ TransactionManager.manage(utx, new Transactional(em) {
+ public void transact() {
+ em.persist(userEntity);
+ em.remove(userEntity);
+ }
+ });
+ }
+
+ @Override
+ public String toString() {
+ return userEntity.getUserid();
+ }
+
+ public UserEntity getUserEntity() {
+ return userEntity;
+ }
+}
diff --git a/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/resource/UsersResource.java b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/resource/UsersResource.java
new file mode 100644
index 0000000..5c7025a
--- /dev/null
+++ b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/resource/UsersResource.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark_em.resource;
+
+import java.net.URI;
+import java.util.List;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import javax.annotation.ManagedBean;
+import javax.annotation.Resource;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.transaction.UserTransaction;
+
+import org.glassfish.jersey.examples.bookmark_em.entity.UserEntity;
+
+import org.codehaus.jettison.json.JSONArray;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("/users/")
+@ManagedBean
+public class UsersResource {
+
+ // injected EntityManager property
+ @PersistenceContext(unitName = "BookmarkPU")
+ private EntityManager em;
+
+ // injected UserTransaction
+ @Resource
+ private UserTransaction utx;
+
+ @Context
+ UriInfo uriInfo;
+
+ /**
+ * Creates a new instance of Users
+ */
+ public UsersResource() {
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<UserEntity> getUsers() {
+ return em.createQuery("SELECT u from UserEntity u").getResultList();
+ }
+
+ @Path("{userid}/")
+ public UserResource getUser(@PathParam("userid") String userid) {
+ return new UserResource(uriInfo, em, utx, userid);
+ }
+
+ @GET
+ @Produces("application/json")
+ public JSONArray getUsersAsJsonArray() {
+ JSONArray uriArray = new JSONArray();
+ for (UserEntity userEntity : getUsers()) {
+ UriBuilder ub = uriInfo.getAbsolutePathBuilder();
+ URI userUri = ub
+ .path(userEntity.getUserid())
+ .build();
+ uriArray.put(userUri.toASCIIString());
+ }
+ return uriArray;
+ }
+}
diff --git a/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/util/tx/TransactionManager.java b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/util/tx/TransactionManager.java
new file mode 100644
index 0000000..5f4d844
--- /dev/null
+++ b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/util/tx/TransactionManager.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark_em.util.tx;
+
+import javax.ws.rs.WebApplicationException;
+
+import javax.transaction.SystemException;
+import javax.transaction.UserTransaction;
+
+/**
+ * @author Paul Sandoz
+ */
+public final class TransactionManager {
+
+ public static void manage(UserTransaction utx, Transactional t) {
+ try {
+ utx.begin();
+ if (t.joinTransaction) {
+ t.em.joinTransaction();
+ }
+ t.transact();
+ utx.commit();
+ } catch (Exception e) {
+ try {
+ utx.rollback();
+ } catch (SystemException se) {
+ throw new WebApplicationException(se);
+ }
+ throw new WebApplicationException(e);
+ }
+ }
+}
diff --git a/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/util/tx/Transactional.java b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/util/tx/Transactional.java
new file mode 100644
index 0000000..78a4d42
--- /dev/null
+++ b/examples/bookmark-em/src/main/java/org/glassfish/jersey/examples/bookmark_em/util/tx/Transactional.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark_em.util.tx;
+
+import javax.persistence.EntityManager;
+
+/**
+ * @author Paul Sandoz
+ */
+public abstract class Transactional {
+
+ EntityManager em;
+ boolean joinTransaction;
+
+ public Transactional(EntityManager em, boolean joinTransaction) {
+ this.em = em;
+ this.joinTransaction = joinTransaction;
+ }
+
+ public Transactional(EntityManager em) {
+ this(em, true);
+ }
+
+ public abstract void transact();
+}
diff --git a/examples/bookmark-em/src/main/resources/META-INF/persistence.xml b/examples/bookmark-em/src/main/resources/META-INF/persistence.xml
new file mode 100644
index 0000000..6df08eb
--- /dev/null
+++ b/examples/bookmark-em/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
+ <persistence-unit name="BookmarkPU" transaction-type="JTA">
+ <jta-data-source>jdbc/__default</jta-data-source>
+ <properties>
+ <!-- drop and create tables at deployment -->
+ <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
+ <!-- log sqls executed in server.log -->
+ <property name="eclipselink.logging.level" value="FINE"/>
+ <!-- Instruct EclipseLink to not log execeptions it throws. Lets the application decide about it-->
+ <property name="eclipselink.logging.exceptions" value="false"/>
+ </properties>
+ </persistence-unit>
+</persistence>
diff --git a/examples/bookmark-em/src/main/webapp/WEB-INF/glassfish-web.xml b/examples/bookmark-em/src/main/webapp/WEB-INF/glassfish-web.xml
new file mode 100644
index 0000000..4ed5567
--- /dev/null
+++ b/examples/bookmark-em/src/main/webapp/WEB-INF/glassfish-web.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
+<glassfish-web-app error-url="">
+ <context-root>/Bookmark-EM</context-root>
+ <class-loader delegate="true" />
+ <jsp-config>
+ <property name="classdebuginfo" value="true">
+ <description>Enable debug info compilation in the generated servlet class</description>
+ </property>
+ <property name="keepgenerated" value="true">
+ <description>Keep a copy of the generated servlet class' java code.</description>
+ </property>
+ <property name="mappedfile" value="true">
+ <description>Maintain a one-to-one correspondence between static content and the generated servlet class' java code</description>
+ </property>
+ </jsp-config>
+</glassfish-web-app>
diff --git a/examples/bookmark-em/src/main/webapp/WEB-INF/web.xml b/examples/bookmark-em/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..e09e223
--- /dev/null
+++ b/examples/bookmark-em/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+ <servlet>
+ <servlet-name>org.glassfish.jersey.examples.bookmark_em.MyApplication</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.glassfish.jersey.examples.bookmark_em.MyApplication</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>org.glassfish.jersey.examples.bookmark_em.MyApplication</servlet-name>
+ <url-pattern>/resources/*</url-pattern>
+ </servlet-mapping>
+
+ <persistence-unit-ref>
+ <persistence-unit-ref-name>persistence/bookmark</persistence-unit-ref-name>
+ <persistence-unit-name>BookmarkPU</persistence-unit-name>
+ </persistence-unit-ref>
+ <resource-ref>
+ <res-ref-name>UserTransaction</res-ref-name>
+ <res-type>javax.transaction.UserTransaction</res-type>
+ <res-auth>Container</res-auth>
+ </resource-ref>
+</web-app>
diff --git a/examples/bookmark-em/src/test/java/org/glassfish/jersey/examples/bookmark_em/BookmarkTest.java b/examples/bookmark-em/src/test/java/org/glassfish/jersey/examples/bookmark_em/BookmarkTest.java
new file mode 100644
index 0000000..0dab7fa
--- /dev/null
+++ b/examples/bookmark-em/src/test/java/org/glassfish/jersey/examples/bookmark_em/BookmarkTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark_em;
+
+import java.net.URI;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.jettison.JettisonFeature;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.external.ExternalTestContainerFactory;
+import org.glassfish.jersey.test.spi.TestContainerException;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+
+import org.codehaus.jettison.json.JSONArray;
+import org.codehaus.jettison.json.JSONObject;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * TODO un-ignore once Jersey supports @ManagedBean
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Michal Gajdos
+ */
+@Ignore("un-ignore once Jersey supports @ManagedBean")
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class BookmarkTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ return new Application();
+ }
+
+ @Override
+ protected URI getBaseUri() {
+ return URI.create(super.getBaseUri().toString() + "Bookmark-EM");
+ }
+
+ @Override
+ protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
+ return new ExternalTestContainerFactory();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(new JettisonFeature());
+ }
+
+ @Test
+ public void step1_getUsers() {
+ JSONArray users = target().path("resources/users/").request("application/json").get(JSONArray.class);
+ assertTrue(users != null);
+ }
+
+ @Test
+ public void step2_createUser() {
+ boolean thrown = false;
+ JSONObject user = new JSONObject();
+
+ try {
+ user.put("userid", "testuid").put("password", "test").put("email", "test@test.net").put("username", "Test User");
+ target().path("resources/users/testuid").request().put(Entity.entity(user, "application/json"));
+ } catch (Exception e) {
+ e.printStackTrace();
+ thrown = true;
+ }
+
+ assertFalse(thrown);
+ }
+
+ @Test
+ public void step3_getUsers2() {
+ JSONArray users = target().path("resources/users/").request("application/json").get(JSONArray.class);
+ assertTrue(users != null);
+ assertTrue(users.length() == 1);
+ }
+
+ @Test
+ public void step4_updateUser() {
+ boolean thrown = false;
+
+ try {
+ JSONObject user = target().path("resources/users/testuid").request("application/json").get(JSONObject.class);
+
+ user.put("password", "NEW PASSWORD").put("email", "NEW@EMAIL.NET").put("username", "UPDATED TEST USER");
+ target().path("resources/users/testuid").request().put(Entity.entity(user, "application/json"));
+
+ user = target().path("resources/users/testuid").request("application/json").get(JSONObject.class);
+
+ assertEquals(user.get("username"), "UPDATED TEST USER");
+ assertEquals(user.get("email"), "NEW@EMAIL.NET");
+ assertEquals(user.get("password"), "NEW PASSWORD");
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ thrown = true;
+ }
+
+ assertFalse(thrown);
+ }
+
+ // this is ugly but it would be probably uglier when divided into separate
+ // test cases
+ @Test
+ public void step5_getUserBookmarkList() {
+ boolean thrown = false;
+
+ try {
+ JSONObject user = target().path("resources/users/testuid").request("application/json").get(JSONObject.class);
+ assertTrue(user != null);
+
+ final WebTarget webTarget = target(user.getString("bookmarks"));
+
+ JSONObject bookmark = new JSONObject();
+ bookmark.put("uri", "http://java.sun.com").put("sdesc", "test desc").put("ldesc", "long test description");
+ webTarget.request().post(Entity.entity(bookmark, "application/json"));
+
+ JSONArray bookmarks = webTarget.request("application/json").get(JSONArray.class);
+ assertTrue(bookmarks != null);
+ int bookmarksSize = bookmarks.length();
+
+ String testBookmarkUrl = bookmarks.getString(0);
+
+ final WebTarget bookmarkResource = target().path(testBookmarkUrl);
+ bookmark = bookmarkResource.request("application/json").get(JSONObject.class);
+ assertTrue(bookmark != null);
+
+ bookmarkResource.request().delete();
+
+ bookmarks = target().path("resources/users/testuid/bookmarks").request("application/json").get(JSONArray.class);
+ assertTrue(bookmarks != null);
+ assertTrue(bookmarks.length() == (bookmarksSize - 1));
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ thrown = true;
+ }
+
+ assertFalse(thrown);
+ }
+
+ @Test
+ public void step6_deleteUser() {
+ boolean thrown = false;
+
+ try {
+ target().path("resources/users/testuid").request().delete();
+ } catch (Exception e) {
+ e.printStackTrace();
+ thrown = true;
+ }
+
+ assertFalse(thrown);
+ }
+}
diff --git a/examples/bookmark/README.MD b/examples/bookmark/README.MD
new file mode 100644
index 0000000..dca924a
--- /dev/null
+++ b/examples/bookmark/README.MD
@@ -0,0 +1,81 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Bookmark Example
+================
+
+This example demonstrates how to use JPA in the backend. The example is
+based on bookmark example from the [RESTful Web
+Services](http://www.oreilly.com/catalog/9780596529260/) book, which was
+inspired by the [http://del.icio.us/](http://del.icio.us/v1) web
+service.
+
+A bookmark web application is presented that is capable of maintaining
+users and their bookmarks.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Notes
+--------------------------------------- | ------------------- | ----------------------------------------------------- | --------------------------------------------------------
+**_/users_** | UsersResource | GET | Returns a list of users.
+**_/users/{userid}_** | UserResource | GET, PUT (used also for creating new users), DELETE | Returns user details
+**_/users/{userid}/bookmarks_** | BookmarksResource | GET, POST | Returns a list of bookmarks for actual user resource.
+**_/users/{userid}/bookmarks/{bmid}_** | BookmarkResource | GET, PUT, DELETE | Returns bookmark uri and a long and short description.
+
+Running the Example
+-------------------
+
+Bookmark example runs on Glassfish 3.x application servers
+([https://javaee.github.io/glassfish/](https://javaee.github.io/glassfish/)) and needs a
+running JavaDB
+(<http://www.oracle.com/technetwork/java/javadb/overview/index.html>)
+instance for underlying data (it comes along with GlassFish).
+
+Presuming, you have installed Glassfish 3.1 `AS_HOME` variable should
+point to your glassfish installation directory.
+
+#### Building And Starting The Bookmark Service
+
+If `.asadminpass` file is missing at your home directory, you will need
+to run the following command to get it created
+
+ $AS_HOME/bin/asadmin start-domain
+ $AS_HOME/bin/asadmin login
+
+You then build and run the example by
+
+ mvn package
+ $AS_HOME/bin/asadmin start-domain
+ $AS_HOME/bin/asadmin start-database
+ $AS_HOME/bin/asadmin create-jdbc-connection-pool \
+ --datasourceclassname org.apache.derby.jdbc.ClientDataSource \
+ --restype javax.sql.DataSource \
+ --property "portnumber=1527:password=REST:user=REST:serverName=localhost:databaseName=BookmarkDB:connectionAttributes=;create\=true" bookmarkPool
+ $AS_HOME/bin/asadmin create-jdbc-resource --connectionpoolid bookmarkPool jdbc/bookmarkSample
+ $AS_HOME/bin/asadmin deploy target/bookmark.war
+
+**Notice:** <span style="color: #f00">On Windows</span>: you might need
+to replace asadmin with asadmin.bat. Also, because it seems there are
+some issues with copy-pasting backslashes, you will also need to use the
+following one-liner instead of the above mentioned complex command:
+
+ asadmin create-jdbc-connection-pool --datasourceclassname org.apache.derby.jdbc.ClientDataSource --restype javax.sql.DataSource --property "portnumber=1527:password=REST:user=REST:serverName=localhost:databaseName=BookmarkDB:connectionAttributes=;create\=true" bookmarkPool
+
+and make sure you do not miss the backslash (\\) in `create\=true` part.
+
+#### Test Client Running
+
+ mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.external.ExternalTestContainerFactory -Djersey.config.test.logging.enable=true -Djersey.config.test.logging.dumpEntity=true -Djersey.config.test.container.port=8080 -Ptest
+
+Links
+-----
+
+<http://java.net/projects/bpcatalog/sources/svn/content/trunk/ee5/docs/persistence/webonlyapp.html?raw=true>
\ No newline at end of file
diff --git a/examples/bookmark/pom.xml b/examples/bookmark/pom.xml
new file mode 100644
index 0000000..4db7fc9
--- /dev/null
+++ b/examples/bookmark/pom.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>bookmark</artifactId>
+ <name>jersey-examples-bookmark</name>
+ <packaging>war</packaging>
+
+ <description>Jersey Bookmark example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jettison</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.persistence</groupId>
+ <artifactId>persistence-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.transaction</groupId>
+ <artifactId>jta</artifactId>
+ <version>1.1</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Run the application using "mvn embedded-glassfish:run" -->
+ <plugin>
+ <groupId>org.glassfish.embedded</groupId>
+ <artifactId>maven-embedded-glassfish-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>run-external-tests</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <jersey.config.test.container.factory>${external.container.factory}</jersey.config.test.container.factory>
+ <jersey.config.test.container.port>${external.container.port}</jersey.config.test.container.port>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <properties>
+ <!-- External test container configuration is done via properties to allow overriding via command line. -->
+ <external.container.factory>org.glassfish.jersey.test.external.ExternalTestContainerFactory</external.container.factory>
+ <external.container.port>8080</external.container.port>
+ <maven.test.skip>false</maven.test.skip>
+ </properties>
+ </profile>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <properties>
+ <maven.test.skip>true</maven.test.skip>
+ </properties>
+
+</project>
diff --git a/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/MyApplication.java b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/MyApplication.java
new file mode 100644
index 0000000..6d875a8
--- /dev/null
+++ b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/MyApplication.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark;
+
+import javax.ws.rs.ApplicationPath;
+
+import org.glassfish.jersey.examples.bookmark.resource.UsersResource;
+import org.glassfish.jersey.jettison.JettisonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * @author Michal Gajdos
+ */
+@ApplicationPath("/")
+public class MyApplication extends ResourceConfig {
+
+ public MyApplication() {
+ registerClasses(UsersResource.class);
+ register(new JettisonFeature());
+ }
+}
diff --git a/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/entity/BookmarkEntity.java b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/entity/BookmarkEntity.java
new file mode 100644
index 0000000..2c692bf
--- /dev/null
+++ b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/entity/BookmarkEntity.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+/**
+ * Entity class BookmarkEntity.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Entity
+@Table(name = "BOOKMARKS")
+@NamedQueries({
+ @NamedQuery(name = "BookmarkEntity.findByUserid",
+ query = "SELECT b FROM BookmarkEntity b WHERE b.bookmarkEntityPK.userid = :userid"),
+ @NamedQuery(name = "BookmarkEntity.findByBmid",
+ query = "SELECT b FROM BookmarkEntity b WHERE b.bookmarkEntityPK.bmid = :bmid"),
+ @NamedQuery(name = "BookmarkEntity.findByUri", query = "SELECT b FROM BookmarkEntity b WHERE b.uri = :uri"),
+ @NamedQuery(name = "BookmarkEntity.findByUpdated", query = "SELECT b FROM BookmarkEntity b WHERE b.updated = :updated"),
+ @NamedQuery(name = "BookmarkEntity.findByLdesc", query = "SELECT b FROM BookmarkEntity b WHERE b.ldesc = :ldesc"),
+ @NamedQuery(name = "BookmarkEntity.findBySdesc", query = "SELECT b FROM BookmarkEntity b WHERE b.sdesc = :sdesc")
+})
+@SuppressWarnings("UnusedDeclaration")
+public class BookmarkEntity implements Serializable {
+
+ /**
+ * EmbeddedId primary key field
+ */
+ @EmbeddedId
+ protected BookmarkEntityPK bookmarkEntityPK;
+
+ @Column(name = "URI", nullable = false)
+ private String uri;
+
+ @Column(name = "UPDATED")
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date updated;
+
+ @Column(name = "LDESC")
+ private String ldesc;
+
+ @Column(name = "SDESC")
+ private String sdesc;
+
+ @JoinColumn(name = "USERID", referencedColumnName = "USERID", insertable = false, updatable = false)
+ @ManyToOne
+ private UserEntity userEntity;
+
+ /**
+ * Creates a new instance of BookmarkEntity
+ */
+ public BookmarkEntity() {
+ }
+
+ /**
+ * Creates a new instance of BookmarkEntity with the specified values.
+ *
+ * @param bookmarkEntityPK the bookmarkEntityPK of the BookmarkEntity
+ */
+ public BookmarkEntity(BookmarkEntityPK bookmarkEntityPK) {
+ this.bookmarkEntityPK = bookmarkEntityPK;
+ }
+
+ /**
+ * Creates a new instance of BookmarkEntity with the specified values.
+ *
+ * @param bookmarkEntityPK the bookmarkEntityPK of the BookmarkEntity
+ * @param uri the uri of the BookmarkEntity
+ */
+ public BookmarkEntity(BookmarkEntityPK bookmarkEntityPK, String uri) {
+ this.bookmarkEntityPK = bookmarkEntityPK;
+ this.uri = uri;
+ }
+
+ /**
+ * Creates a new instance of BookmarkEntityPK with the specified values.
+ *
+ * @param bmid the bmid of the BookmarkEntityPK
+ * @param userid the userid of the BookmarkEntityPK
+ */
+ public BookmarkEntity(String bmid, String userid) {
+ this.bookmarkEntityPK = new BookmarkEntityPK(bmid, userid);
+ }
+
+ /**
+ * Gets the bookmarkEntityPK of this BookmarkEntity.
+ *
+ * @return the bookmarkEntityPK
+ */
+ public BookmarkEntityPK getBookmarkEntityPK() {
+ return this.bookmarkEntityPK;
+ }
+
+ /**
+ * Sets the bookmarkEntityPK of this BookmarkEntity to the specified value.
+ *
+ * @param bookmarkEntityPK the new bookmarkEntityPK
+ */
+ public void setBookmarkEntityPK(BookmarkEntityPK bookmarkEntityPK) {
+ this.bookmarkEntityPK = bookmarkEntityPK;
+ }
+
+ /**
+ * Gets the uri of this BookmarkEntity.
+ *
+ * @return the uri
+ */
+ public String getUri() {
+ return this.uri;
+ }
+
+ /**
+ * Sets the uri of this BookmarkEntity to the specified value.
+ *
+ * @param uri the new uri
+ */
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ /**
+ * Gets the updated of this BookmarkEntity.
+ *
+ * @return the updated
+ */
+ public Date getUpdated() {
+ return this.updated;
+ }
+
+ /**
+ * Sets the updated of this BookmarkEntity to the specified value.
+ *
+ * @param updated the new updated
+ */
+ public void setUpdated(Date updated) {
+ this.updated = updated;
+ }
+
+ /**
+ * Gets the ldesc of this BookmarkEntity.
+ *
+ * @return the ldesc
+ */
+ public String getLdesc() {
+ return this.ldesc;
+ }
+
+ /**
+ * Sets the ldesc of this BookmarkEntity to the specified value.
+ *
+ * @param ldesc the new ldesc
+ */
+ public void setLdesc(String ldesc) {
+ this.ldesc = ldesc;
+ }
+
+ /**
+ * Gets the sdesc of this BookmarkEntity.
+ *
+ * @return the sdesc
+ */
+ public String getSdesc() {
+ return this.sdesc;
+ }
+
+ /**
+ * Sets the sdesc of this BookmarkEntity to the specified value.
+ *
+ * @param sdesc the new sdesc
+ */
+ public void setSdesc(String sdesc) {
+ this.sdesc = sdesc;
+ }
+
+ /**
+ * Gets the userEntity of this BookmarkEntity.
+ *
+ * @return the userEntity
+ */
+ public UserEntity getUserEntity() {
+ return this.userEntity;
+ }
+
+ /**
+ * Sets the userEntity of this BookmarkEntity to the specified value.
+ *
+ * @param userEntity the new userEntity
+ */
+ public void setUserEntity(UserEntity userEntity) {
+ this.userEntity = userEntity;
+ }
+
+ /**
+ * Returns a hash code value for the object. This implementation computes
+ * a hash code value based on the id fields in this object.
+ *
+ * @return a hash code value for this object.
+ */
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ hash += (this.bookmarkEntityPK != null ? this.bookmarkEntityPK.hashCode() : 0);
+ return hash;
+ }
+
+ /**
+ * Determines whether another object is equal to this BookmarkEntity. The result is
+ * <code>true</code> if and only if the argument is not null and is a BookmarkEntity object that
+ * has the same id field values as this object.
+ *
+ * @param object the reference object with which to compare
+ * @return <code>true</code> if this object is the same as the argument;
+ * <code>false</code> otherwise.
+ */
+ @Override
+ public boolean equals(Object object) {
+ // TODO: Warning - this method won't work in the case the id fields are not set
+ if (!(object instanceof BookmarkEntity)) {
+ return false;
+ }
+ BookmarkEntity other = (BookmarkEntity) object;
+ return !(this.bookmarkEntityPK != other.bookmarkEntityPK && (this.bookmarkEntityPK == null || !this.bookmarkEntityPK
+ .equals(other.bookmarkEntityPK)));
+ }
+
+ /**
+ * Returns a string representation of the object. This implementation constructs
+ * that representation based on the id fields.
+ *
+ * @return a string representation of the object.
+ */
+ @Override
+ public String toString() {
+ return "BookmarkEntity{"
+ + "bookmarkEntityPK=" + bookmarkEntityPK
+ + '}';
+ }
+}
diff --git a/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/entity/BookmarkEntityPK.java b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/entity/BookmarkEntityPK.java
new file mode 100644
index 0000000..144bc8a
--- /dev/null
+++ b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/entity/BookmarkEntityPK.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark.entity;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+/**
+ * Primary Key class BookmarkEntityPK for entity class BookmarkEntity.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@SuppressWarnings("UnusedDeclaration")
+@Embeddable
+public class BookmarkEntityPK implements Serializable {
+
+ @Column(name = "USERID", nullable = false)
+ private String userid;
+
+ @Column(name = "BMID", nullable = false)
+ private String bmid;
+
+ /**
+ * Creates a new instance of BookmarkEntityPK
+ */
+ public BookmarkEntityPK() {
+ }
+
+ /**
+ * Creates a new instance of BookmarkEntityPK with the specified values.
+ *
+ * @param bmid the bmid of the BookmarkEntityPK
+ * @param userid the userid of the BookmarkEntityPK
+ */
+ public BookmarkEntityPK(String bmid, String userid) {
+ this.bmid = bmid;
+ this.userid = userid;
+ }
+
+ /**
+ * Gets the userid of this BookmarkEntityPK.
+ *
+ * @return the userid
+ */
+ public String getUserid() {
+ return this.userid;
+ }
+
+ /**
+ * Sets the userid of this BookmarkEntityPK to the specified value.
+ *
+ * @param userid the new userid
+ */
+ public void setUserid(String userid) {
+ this.userid = userid;
+ }
+
+ /**
+ * Gets the bmid of this BookmarkEntityPK.
+ *
+ * @return the bmid
+ */
+ public String getBmid() {
+ return this.bmid;
+ }
+
+ /**
+ * Sets the bmid of this BookmarkEntityPK to the specified value.
+ *
+ * @param bmid the new bmid
+ */
+ public void setBmid(String bmid) {
+ this.bmid = bmid;
+ }
+
+ /**
+ * Returns a hash code value for the object. This implementation computes
+ * a hash code value based on the id fields in this object.
+ *
+ * @return a hash code value for this object.
+ */
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ hash += (this.bmid != null ? this.bmid.hashCode() : 0);
+ hash += (this.userid != null ? this.userid.hashCode() : 0);
+ return hash;
+ }
+
+ /**
+ * Determines whether another object is equal to this BookmarkEntityPK. The result is
+ * <code>true</code> if and only if the argument is not null and is a BookmarkEntityPK object that
+ * has the same id field values as this object.
+ *
+ * @param object the reference object with which to compare
+ * @return <code>true</code> if this object is the same as the argument;
+ * <code>false</code> otherwise.
+ */
+ @Override
+ @SuppressWarnings("StringEquality")
+ public boolean equals(Object object) {
+ // TODO: Warning - this method won't work in the case the id fields are not set
+ if (!(object instanceof BookmarkEntityPK)) {
+ return false;
+ }
+
+ BookmarkEntityPK other = (BookmarkEntityPK) object;
+ if (this.bmid != other.bmid && (this.bmid == null || !this.bmid.equals(other.bmid))) {
+ return false;
+ }
+ if (this.userid != other.userid && (this.userid == null || !this.userid.equals(other.userid))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns a string representation of the object. This implementation constructs
+ * that representation based on the id fields.
+ *
+ * @return a string representation of the object.
+ */
+ @Override
+ public String toString() {
+ return "BookmarkEntityPK{"
+ + "userid='" + userid + '\''
+ + ", bmid='" + bmid + '\''
+ + '}';
+ }
+}
diff --git a/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/entity/UserEntity.java b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/entity/UserEntity.java
new file mode 100644
index 0000000..425abe2
--- /dev/null
+++ b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/entity/UserEntity.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark.entity;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+/**
+ * Entity class UserEntity.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@SuppressWarnings("UnusedDeclaration")
+@Entity
+@Table(name = "USERS")
+@NamedQueries({
+ @NamedQuery(name = "UserEntity.findByUserid", query = "SELECT u FROM UserEntity u WHERE u.userid = :userid"),
+ @NamedQuery(name = "UserEntity.findByPassword", query = "SELECT u FROM UserEntity u WHERE u.password = :password"),
+ @NamedQuery(name = "UserEntity.findByUsername", query = "SELECT u FROM UserEntity u WHERE u.username = :username"),
+ @NamedQuery(name = "UserEntity.findByEmail", query = "SELECT u FROM UserEntity u WHERE u.email = :email")
+})
+public class UserEntity implements Serializable {
+
+ @Id
+ @Column(name = "USERID", nullable = false)
+ private String userid;
+
+ @Column(name = "PASSWORD", nullable = false)
+ private String password;
+
+ @Column(name = "USERNAME")
+ private String username;
+
+ @Column(name = "EMAIL")
+ private String email;
+
+ @OneToMany(cascade = CascadeType.ALL, mappedBy = "userEntity")
+ private Collection<BookmarkEntity> bookmarkEntityCollection;
+
+ /**
+ * Creates a new instance of UserEntity
+ */
+ public UserEntity() {
+ }
+
+ /**
+ * Creates a new instance of UserEntity with the specified values.
+ *
+ * @param userid the userid of the UserEntity
+ */
+ public UserEntity(String userid) {
+ this.userid = userid;
+ }
+
+ /**
+ * Creates a new instance of UserEntity with the specified values.
+ *
+ * @param userid the userid of the UserEntity
+ * @param password the password of the UserEntity
+ */
+ public UserEntity(String userid, String password) {
+ this.userid = userid;
+ this.password = password;
+ }
+
+ /**
+ * Gets the userid of this UserEntity.
+ *
+ * @return the userid
+ */
+ public String getUserid() {
+ return this.userid;
+ }
+
+ /**
+ * Sets the userid of this UserEntity to the specified value.
+ *
+ * @param userid the new userid
+ */
+ public void setUserid(String userid) {
+ this.userid = userid;
+ }
+
+ /**
+ * Gets the password of this UserEntity.
+ *
+ * @return the password
+ */
+ public String getPassword() {
+ return this.password;
+ }
+
+ /**
+ * Sets the password of this UserEntity to the specified value.
+ *
+ * @param password the new password
+ */
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ /**
+ * Gets the username of this UserEntity.
+ *
+ * @return the username
+ */
+ public String getUsername() {
+ return this.username;
+ }
+
+ /**
+ * Sets the username of this UserEntity to the specified value.
+ *
+ * @param username the new username
+ */
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ /**
+ * Gets the email of this UserEntity.
+ *
+ * @return the email
+ */
+ public String getEmail() {
+ return this.email;
+ }
+
+ /**
+ * Sets the email of this UserEntity to the specified value.
+ *
+ * @param email the new email
+ */
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ /**
+ * Gets the bookmarkEntityCollection of this UserEntity.
+ *
+ * @return the bookmarkEntityCollection
+ */
+ public Collection<BookmarkEntity> getBookmarkEntityCollection() {
+ return this.bookmarkEntityCollection;
+ }
+
+ /**
+ * Sets the bookmarkEntityCollection of this UserEntity to the specified value.
+ *
+ * @param bookmarkEntityCollection the new bookmarkEntityCollection
+ */
+ public void setBookmarkEntityCollection(Collection<BookmarkEntity> bookmarkEntityCollection) {
+ this.bookmarkEntityCollection = bookmarkEntityCollection;
+ }
+
+ /**
+ * Returns a hash code value for the object. This implementation computes
+ * a hash code value based on the id fields in this object.
+ *
+ * @return a hash code value for this object.
+ */
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ hash += (this.userid != null ? this.userid.hashCode() : 0);
+ return hash;
+ }
+
+ /**
+ * Determines whether another object is equal to this UserEntity. The result is
+ * <code>true</code> if and only if the argument is not null and is a UserEntity object that
+ * has the same id field values as this object.
+ *
+ * @param object the reference object with which to compare
+ * @return <code>true</code> if this object is the same as the argument;
+ * <code>false</code> otherwise.
+ */
+ @SuppressWarnings("StringEquality")
+ @Override
+ public boolean equals(Object object) {
+ // TODO: Warning - this method won't work in the case the id fields are not set
+ if (!(object instanceof UserEntity)) {
+ return false;
+ }
+
+ UserEntity other = (UserEntity) object;
+
+ return !(this.userid != other.userid && (this.userid == null || !this.userid.equals(other.userid)));
+ }
+
+ /**
+ * Returns a string representation of the object. This implementation constructs
+ * that representation based on the id fields.
+ *
+ * @return a string representation of the object.
+ */
+ @Override
+ public String toString() {
+ return "UserEntity{"
+ + "userid='" + userid + '\''
+ + '}';
+ }
+}
diff --git a/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/exception/ExtendedNotFoundException.java b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/exception/ExtendedNotFoundException.java
new file mode 100644
index 0000000..3417426
--- /dev/null
+++ b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/exception/ExtendedNotFoundException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark.exception;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author Michal Gajdos
+ */
+public class ExtendedNotFoundException extends NotFoundException {
+
+ public ExtendedNotFoundException(final String message) {
+ super(Response.status(Response.Status.NOT_FOUND).entity(message).build());
+ }
+}
diff --git a/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/resource/BookmarkResource.java b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/resource/BookmarkResource.java
new file mode 100644
index 0000000..6ed3f0d
--- /dev/null
+++ b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/resource/BookmarkResource.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark.resource;
+
+import java.util.Date;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import javax.persistence.EntityManager;
+
+import org.glassfish.jersey.examples.bookmark.entity.BookmarkEntity;
+import org.glassfish.jersey.examples.bookmark.entity.BookmarkEntityPK;
+import org.glassfish.jersey.examples.bookmark.entity.UserEntity;
+import org.glassfish.jersey.examples.bookmark.exception.ExtendedNotFoundException;
+import org.glassfish.jersey.examples.bookmark.util.tx.TransactionManager;
+import org.glassfish.jersey.examples.bookmark.util.tx.Transactional;
+
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+public class BookmarkResource {
+
+ UriInfo uriInfo; // actual uri info provided by parent resource
+ EntityManager em; // entity manager provided by parent resource
+
+ BookmarkEntity bookmarkEntity;
+
+ /**
+ * Creates a new instance of UserResource
+ */
+ public BookmarkResource(UriInfo uriInfo, EntityManager em, UserEntity userEntity, String bmid) {
+ this.uriInfo = uriInfo;
+ this.em = em;
+ bookmarkEntity = em.find(BookmarkEntity.class, new BookmarkEntityPK(bmid, userEntity.getUserid()));
+ if (null == bookmarkEntity) {
+ throw new ExtendedNotFoundException("bookmark with userid="
+ + userEntity.getUserid() + " and bmid="
+ + bmid + " not found\n");
+ }
+ bookmarkEntity.setUserEntity(userEntity);
+ }
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public JSONObject getBookmark() {
+ return asJson();
+ }
+
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void putBookmark(JSONObject jsonEntity) throws JSONException {
+
+ bookmarkEntity.setLdesc(jsonEntity.getString("ldesc"));
+ bookmarkEntity.setSdesc(jsonEntity.getString("sdesc"));
+ bookmarkEntity.setUpdated(new Date());
+
+ TransactionManager.manage(new Transactional(em) {
+ public void transact() {
+ em.merge(bookmarkEntity);
+ }
+ });
+ }
+
+ @DELETE
+ public void deleteBookmark() {
+ TransactionManager.manage(new Transactional(em) {
+ public void transact() {
+ UserEntity userEntity = bookmarkEntity.getUserEntity();
+ userEntity.getBookmarkEntityCollection().remove(bookmarkEntity);
+ em.merge(userEntity);
+ em.remove(bookmarkEntity);
+ }
+ });
+ }
+
+ public JSONObject asJson() {
+ try {
+ return new JSONObject().put("userid", bookmarkEntity.getBookmarkEntityPK().getUserid())
+ .put("sdesc", bookmarkEntity.getSdesc())
+ .put("ldesc", bookmarkEntity.getLdesc())
+ .put("uri", bookmarkEntity.getUri());
+ } catch (JSONException je) {
+ return null;
+ }
+ }
+
+ public String toString() {
+ return bookmarkEntity.getBookmarkEntityPK().getUserid();
+ }
+}
diff --git a/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/resource/BookmarksResource.java b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/resource/BookmarksResource.java
new file mode 100644
index 0000000..c03aaa5
--- /dev/null
+++ b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/resource/BookmarksResource.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark.resource;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.Date;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import javax.persistence.EntityManager;
+
+import org.glassfish.jersey.examples.bookmark.entity.BookmarkEntity;
+import org.glassfish.jersey.examples.bookmark.util.tx.TransactionManager;
+import org.glassfish.jersey.examples.bookmark.util.tx.Transactional;
+
+import org.codehaus.jettison.json.JSONArray;
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+public class BookmarksResource {
+
+ UriInfo uriInfo; // actual uri info
+ EntityManager em; // entity manager provided by parent resource
+
+ UserResource userResource; // parent user resource
+
+ /**
+ * Creates a new instance of BookmarksResource
+ */
+ public BookmarksResource(UriInfo uriInfo, EntityManager em, UserResource userResource) {
+ this.uriInfo = uriInfo;
+ this.em = em;
+ this.userResource = userResource;
+ }
+
+ public Collection<BookmarkEntity> getBookmarks() {
+ return userResource.getUserEntity().getBookmarkEntityCollection();
+ }
+
+ @Path("{bmid: .+}")
+ public BookmarkResource getBookmark(@PathParam("bmid") String bmid) {
+ return new BookmarkResource(uriInfo, em, userResource.getUserEntity(), bmid);
+ }
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public JSONArray getBookmarksAsJsonArray() {
+ JSONArray uriArray = new JSONArray();
+ for (BookmarkEntity bookmarkEntity : getBookmarks()) {
+ UriBuilder ub = uriInfo.getAbsolutePathBuilder();
+ URI bookmarkUri = ub
+ .path(bookmarkEntity.getBookmarkEntityPK().getBmid())
+ .build();
+ uriArray.put(bookmarkUri.toASCIIString());
+ }
+ return uriArray;
+ }
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response postForm(JSONObject bookmark) throws JSONException {
+ final BookmarkEntity bookmarkEntity = new BookmarkEntity(getBookmarkId(bookmark.getString("uri")),
+ userResource.getUserEntity().getUserid());
+
+ bookmarkEntity.setUri(bookmark.getString("uri"));
+ bookmarkEntity.setUpdated(new Date());
+ bookmarkEntity.setSdesc(bookmark.getString("sdesc"));
+ bookmarkEntity.setLdesc(bookmark.getString("ldesc"));
+ userResource.getUserEntity().getBookmarkEntityCollection().add(bookmarkEntity);
+
+ TransactionManager.manage(new Transactional(em) {
+ public void transact() {
+ em.merge(userResource.getUserEntity());
+ }
+ });
+
+ URI bookmarkUri = uriInfo.getAbsolutePathBuilder()
+ .path(bookmarkEntity.getBookmarkEntityPK().getBmid())
+ .build();
+ return Response.created(bookmarkUri).build();
+ }
+
+ private String getBookmarkId(String uri) {
+ return uri;
+ }
+}
diff --git a/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/resource/UserResource.java b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/resource/UserResource.java
new file mode 100644
index 0000000..1de92f8
--- /dev/null
+++ b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/resource/UserResource.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark.resource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import javax.persistence.EntityManager;
+
+import org.glassfish.jersey.examples.bookmark.entity.UserEntity;
+import org.glassfish.jersey.examples.bookmark.exception.ExtendedNotFoundException;
+import org.glassfish.jersey.examples.bookmark.util.tx.TransactionManager;
+import org.glassfish.jersey.examples.bookmark.util.tx.Transactional;
+
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+public class UserResource {
+
+ String userid; // userid from url
+ UserEntity userEntity; // appropriate jpa user entity
+
+ UriInfo uriInfo; // actual uri info provided by parent resource
+ EntityManager em; // entity manager provided by parent resource
+
+ /**
+ * Creates a new instance of UserResource
+ */
+ public UserResource(UriInfo uriInfo, EntityManager em, String userid) {
+ this.uriInfo = uriInfo;
+ this.userid = userid;
+ this.em = em;
+ userEntity = em.find(UserEntity.class, userid);
+ }
+
+ @Path("bookmarks/")
+ public BookmarksResource getBookmarksResource() {
+ if (null == userEntity) {
+ throw new ExtendedNotFoundException("userid " + userid + " does not exist!");
+ }
+ return new BookmarksResource(uriInfo, em, this);
+ }
+
+ @GET
+ @Produces("application/json")
+ public JSONObject getUser() throws JSONException {
+ if (null == userEntity) {
+ throw new ExtendedNotFoundException("userid " + userid + "does not exist!");
+ }
+ return new JSONObject().put("userid", userEntity.getUserid())
+ .put("username", userEntity.getUsername())
+ .put("email", userEntity.getEmail())
+ .put("password", userEntity.getPassword())
+ .put("bookmarks", uriInfo.getAbsolutePathBuilder().path("bookmarks").build());
+ }
+
+ @PUT
+ @Consumes("application/json")
+ public Response putUser(JSONObject jsonEntity) throws JSONException {
+
+ String jsonUserid = jsonEntity.getString("userid");
+
+ if ((null != jsonUserid) && !jsonUserid.equals(userid)) {
+ return Response.status(409).entity("userids differ!\n").build();
+ }
+
+ final boolean newRecord = (null == userEntity); // insert or update ?
+
+ if (newRecord) { // new user record to be inserted
+ userEntity = new UserEntity();
+ userEntity.setUserid(userid);
+ }
+ userEntity.setUsername(jsonEntity.getString("username"));
+ userEntity.setEmail(jsonEntity.getString("email"));
+ userEntity.setPassword(jsonEntity.getString("password"));
+
+ if (newRecord) {
+ TransactionManager.manage(new Transactional(em) {
+ public void transact() {
+ em.persist(userEntity);
+ }
+ });
+ return Response.created(uriInfo.getAbsolutePath()).build();
+ } else {
+ TransactionManager.manage(new Transactional(em) {
+ public void transact() {
+ em.merge(userEntity);
+ }
+ });
+ return Response.noContent().build();
+ }
+ }
+
+ @DELETE
+ public void deleteUser() {
+ if (null == userEntity) {
+ throw new ExtendedNotFoundException("userid " + userid + "does not exist!");
+ }
+ TransactionManager.manage(new Transactional(em) {
+ public void transact() {
+ em.remove(userEntity);
+ }
+ });
+ }
+
+ public String toString() {
+ return userEntity.getUserid();
+ }
+
+ public UserEntity getUserEntity() {
+ return userEntity;
+ }
+}
diff --git a/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/resource/UsersResource.java b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/resource/UsersResource.java
new file mode 100644
index 0000000..69b4b0c
--- /dev/null
+++ b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/resource/UsersResource.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark.resource;
+
+import java.net.URI;
+import java.util.List;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.PersistenceUnit;
+
+import org.glassfish.jersey.examples.bookmark.entity.UserEntity;
+
+import org.codehaus.jettison.json.JSONArray;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("/users/")
+public class UsersResource {
+
+ @Context
+ UriInfo uriInfo;
+
+ @PersistenceUnit(unitName = "BookmarkPU")
+ EntityManagerFactory emf;
+
+ /**
+ * Creates a new instance of Users.
+ */
+ public UsersResource() {
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<UserEntity> getUsers() {
+ return emf.createEntityManager().createQuery("SELECT u from UserEntity u").getResultList();
+ }
+
+ @Path("{userid}/")
+ public UserResource getUser(@PathParam("userid") String userid) {
+ return new UserResource(uriInfo, emf.createEntityManager(), userid);
+ }
+
+ @GET
+ @Produces("application/json")
+ public JSONArray getUsersAsJsonArray() {
+ JSONArray uriArray = new JSONArray();
+ for (UserEntity userEntity : getUsers()) {
+ UriBuilder ub = uriInfo.getAbsolutePathBuilder();
+ URI userUri = ub
+ .path(userEntity.getUserid())
+ .build();
+ uriArray.put(userUri.toASCIIString());
+ }
+ return uriArray;
+ }
+}
diff --git a/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/util/tx/TransactionManager.java b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/util/tx/TransactionManager.java
new file mode 100644
index 0000000..856a90d
--- /dev/null
+++ b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/util/tx/TransactionManager.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark.util.tx;
+
+import javax.ws.rs.WebApplicationException;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.transaction.SystemException;
+import javax.transaction.UserTransaction;
+
+/**
+ * @author Paul Sandoz
+ */
+public final class TransactionManager {
+
+ public static void manage(Transactional t) {
+ UserTransaction utx = getUtx();
+ try {
+ utx.begin();
+ if (t.joinTransaction) {
+ t.em.joinTransaction();
+ }
+ t.transact();
+ utx.commit();
+ } catch (Exception e) {
+ try {
+ utx.rollback();
+ } catch (SystemException se) {
+ throw new WebApplicationException(se);
+ }
+ throw new WebApplicationException(e);
+ } finally {
+ t.em.close();
+ }
+ }
+
+ private static UserTransaction getUtx() {
+ try {
+ InitialContext ic = new InitialContext();
+ return (UserTransaction) ic.lookup("java:comp/UserTransaction");
+ } catch (NamingException ne) {
+ throw new WebApplicationException(ne);
+ }
+ }
+}
diff --git a/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/util/tx/Transactional.java b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/util/tx/Transactional.java
new file mode 100644
index 0000000..4c88d06
--- /dev/null
+++ b/examples/bookmark/src/main/java/org/glassfish/jersey/examples/bookmark/util/tx/Transactional.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark.util.tx;
+
+import javax.persistence.EntityManager;
+
+/**
+ * @author Paul Sandoz
+ */
+public abstract class Transactional {
+
+ EntityManager em;
+ boolean joinTransaction;
+
+ public Transactional(EntityManager em, boolean joinTransaction) {
+ this.em = em;
+ this.joinTransaction = joinTransaction;
+ }
+
+ public Transactional(EntityManager em) {
+ this(em, true);
+ }
+
+ public abstract void transact();
+}
diff --git a/examples/bookmark/src/main/resources/META-INF/persistence.xml b/examples/bookmark/src/main/resources/META-INF/persistence.xml
new file mode 100644
index 0000000..5708414
--- /dev/null
+++ b/examples/bookmark/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
+ <persistence-unit name="BookmarkPU" transaction-type="JTA">
+ <provider>oracle.toplink.essentials.PersistenceProvider</provider>
+ <properties>
+ <property name="toplink.jdbc.url" value="jdbc:derby://localhost:1527/BookmarkDB"/>
+ <property name="toplink.jdbc.user" value="REST"/>
+ <property name="toplink.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
+ <property name="toplink.jdbc.password" value="REST"/>
+ <property name="toplink.ddl-generation" value="drop-and-create-tables"/>
+ </properties>
+ </persistence-unit>
+</persistence>
diff --git a/examples/bookmark/src/main/webapp/WEB-INF/glassfish-web.xml b/examples/bookmark/src/main/webapp/WEB-INF/glassfish-web.xml
new file mode 100644
index 0000000..485abd8
--- /dev/null
+++ b/examples/bookmark/src/main/webapp/WEB-INF/glassfish-web.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
+<glassfish-web-app error-url="">
+ <context-root>/bookmark</context-root>
+ <class-loader delegate="true" />
+ <jsp-config>
+ <property name="classdebuginfo" value="true">
+ <description>Enable debug info compilation in the generated servlet class</description>
+ </property>
+ <property name="keepgenerated" value="true">
+ <description>Keep a copy of the generated servlet class' java code.</description>
+ </property>
+ <property name="mappedfile" value="true">
+ <description>Maintain a one-to-one correspondence between static content and the generated servlet class' java code</description>
+ </property>
+ </jsp-config>
+</glassfish-web-app>
diff --git a/examples/bookmark/src/main/webapp/WEB-INF/web.xml b/examples/bookmark/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..d64c5f1
--- /dev/null
+++ b/examples/bookmark/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+ <servlet>
+ <servlet-name>org.glassfish.jersey.examples.bookmark.MyApplication</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.glassfish.jersey.examples.bookmark.MyApplication</param-value>
+ </init-param>
+ <init-param>
+ <param-name>unit:BookmarkPU</param-name>
+ <param-value>persistence/bookmark</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>org.glassfish.jersey.examples.bookmark.MyApplication</servlet-name>
+ <url-pattern>/resources/*</url-pattern>
+ </servlet-mapping>
+
+ <persistence-unit-ref>
+ <persistence-unit-ref-name>persistence/bookmark</persistence-unit-ref-name>
+ <persistence-unit-name>BookmarkPU</persistence-unit-name>
+ </persistence-unit-ref>
+ <resource-ref>
+ <res-ref-name>UserTransaction</res-ref-name>
+ <res-type>javax.transaction.UserTransaction</res-type>
+ <res-auth>Container</res-auth>
+ </resource-ref>
+</web-app>
diff --git a/examples/bookmark/src/test/java/org/glassfish/jersey/examples/bookmark/BookmarkTest.java b/examples/bookmark/src/test/java/org/glassfish/jersey/examples/bookmark/BookmarkTest.java
new file mode 100644
index 0000000..80e8637
--- /dev/null
+++ b/examples/bookmark/src/test/java/org/glassfish/jersey/examples/bookmark/BookmarkTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookmark;
+
+import java.net.URI;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.jettison.JettisonFeature;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.external.ExternalTestContainerFactory;
+import org.glassfish.jersey.test.spi.TestContainerException;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+
+import org.codehaus.jettison.json.JSONArray;
+import org.codehaus.jettison.json.JSONObject;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Michal Gajdos
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class BookmarkTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ return new MyApplication();
+ }
+
+ @Override
+ protected URI getBaseUri() {
+ return UriBuilder.fromUri(super.getBaseUri()).path("bookmark").build();
+ }
+
+ @Override
+ protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
+ return new ExternalTestContainerFactory();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(JettisonFeature.class);
+ }
+
+ @Test
+ public void step1_getUsers() {
+ final JSONArray users = target().path("resources/users/").request("application/json").get(JSONArray.class);
+ assertTrue(users != null);
+ }
+
+ @Test
+ public void step2_createUser() {
+ boolean thrown = false;
+ JSONObject user = new JSONObject();
+
+ try {
+ user.put("userid", "testuid").put("password", "test").put("email", "test@test.net").put("username", "Test User");
+ target().path("resources/users/testuid").request().put(Entity.entity(user, "application/json"));
+ } catch (Exception e) {
+ e.printStackTrace();
+ thrown = true;
+ }
+
+ assertFalse(thrown);
+ }
+
+ @Test
+ public void step3_getUsers2() {
+ final JSONArray users = target().path("resources/users/").request("application/json").get(JSONArray.class);
+ assertTrue(users != null);
+ assertTrue(users.length() == 1);
+ }
+
+ @Test
+ public void step4_updateUser() {
+ boolean thrown = false;
+
+ try {
+ JSONObject user = target().path("resources/users/testuid").request("application/json").get(JSONObject.class);
+
+ user.put("password", "NEW PASSWORD").put("email", "NEW@EMAIL.NET").put("username", "UPDATED TEST USER");
+ target().path("resources/users/testuid").request().put(Entity.entity(user, "application/json"));
+
+ user = target().path("resources/users/testuid").request("application/json").get(JSONObject.class);
+
+ assertEquals(user.get("username"), "UPDATED TEST USER");
+ assertEquals(user.get("email"), "NEW@EMAIL.NET");
+ assertEquals(user.get("password"), "NEW PASSWORD");
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ thrown = true;
+ }
+
+ assertFalse(thrown);
+ }
+
+ // ugly.. but separation into separate test cases would be probably uglier
+ @Test
+ public void step5_getUserBookmarkList() {
+ boolean thrown = false;
+
+ try {
+ JSONObject user = target().path("resources/users/testuid").request("application/json").get(JSONObject.class);
+ assertTrue(user != null);
+
+ final WebTarget webTarget = client().target(user.getString("bookmarks"));
+
+ JSONObject bookmark = new JSONObject();
+ bookmark.put("uri", "http://java.sun.com").put("sdesc", "test desc").put("ldesc", "long test description");
+ webTarget.request().post(Entity.entity(bookmark, "application/json"));
+
+ JSONArray bookmarks = webTarget.request("application/json").get(JSONArray.class);
+ assertTrue(bookmarks != null);
+ int bookmarksSize = bookmarks.length();
+
+ String testBookmarkUrl = bookmarks.getString(0);
+ final WebTarget bookmarkResource = client().target(testBookmarkUrl);
+
+ bookmark = bookmarkResource.request("application/json").get(JSONObject.class);
+ assertTrue(bookmark != null);
+
+ bookmarkResource.request().delete();
+
+ bookmarks = target().path("resources/users/testuid/bookmarks").request("application/json").get(JSONArray.class);
+ assertTrue(bookmarks != null);
+ assertTrue(bookmarks.length() == (bookmarksSize - 1));
+ } catch (Exception e) {
+ e.printStackTrace();
+ thrown = true;
+ }
+
+ assertFalse(thrown);
+ }
+
+ @Test
+ public void step6_deleteUser() {
+ boolean thrown = false;
+
+ try {
+ target().path("resources/users/testuid").request().delete();
+ } catch (Exception e) {
+ e.printStackTrace();
+ thrown = true;
+ }
+
+ assertFalse(thrown);
+ }
+}
diff --git a/examples/bookstore-webapp/README.MD b/examples/bookstore-webapp/README.MD
new file mode 100644
index 0000000..3921d7f
--- /dev/null
+++ b/examples/bookstore-webapp/README.MD
@@ -0,0 +1,125 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Bookstore
+=========
+
+This example demonstrates how to connect JSP pages to resources. The
+example is copied from the
+[Bookstore](http://stapler.kohsuke.org/getting-started.html) example
+presented by the [Stapler](http://stapler.kohsuke.org/) project that
+provides a way to staple URIs to Java classes to build RESTful Web
+applications such as [Hudson](http://java.net/projects/hudson/).
+
+A bookstore Web application is presented that is capable of presenting
+books, CDs and tracks of CDs.
+
+Contents
+--------
+
+The example consists of four web resources implemented by the following:
+
+`org.glassfish.jersey.examples.bookstore.webapp.resource.Bookstore`
+
+The bookstore resource that returns a list of items, either CDs
+or Books. The resource dynamically references a Book or CD resource
+using the getItem method that is annotated with Path. The Book and
+CD resource both inherit from the Item class and thus the resources
+can be managed polymorphically.
+
+`org.glassfish.jersey.examples.bookstore.webapp.resource.Book`
+
+The book resource that has a title and an author.
+
+`org.glassfish.jersey.examples.bookstore.webapp.resource.CD`
+
+The CD resource that has a title, author and a list of tracks. The
+resource dynamically references the Track resource using the
+getTrack method that is annotated with Path.
+
+`org.glassfish.jersey.examples.bookstore.webapp.resource.Track`
+
+The Tracks resource that has a name and the length of the track.
+
+The mapping of the URI path space is presented in the following table:
+
+
+URI path | Resource class | HTTP methods | Notes
+----------------------------------- | ---------------- | -------------- | ------------------------------------------------------------------------------
+**_/_** | Bookstore | GET | webapp/resource/Bookstore/index.jsp
+**_/count_** | Bookstore | GET | webapp/resource/Bookstore/count.jsp
+**_/time_** | Bookstore | GET | webapp/resource/Bookstore/time.jsp
+**_/items/{itemid}_** | Book, CD | GET | webapp/resource/Book/index.jsp, webapp/resource/CD/index.jsp
+**_/items/{itemid}/tracks/{num}_** | Track | GET | webapp/resource/Track/index.jsp
+
+Running the Example
+-------------------
+
+Bookstore example runs on Glassfish 4.0 application server
+([https://javaee.github.io/glassfish/](https://javaee.github.io/glassfish/)), which can be
+run as an embedded container or Jetty.
+
+If you are working with Jersey GlassFish update center module installed
+into your existing GlassFish instance, you will need to follow
+instructions at [the module README file](../../README.html) in order to
+deploy the example.
+
+Otherwise, you can run the example using embedded GlassFish as follows:
+
+Build and deploy the project by executing the following command from the
+project directory
+
+> mvn clean package embedded-glassfish:run (TODO does not work at the moment)
+
+or you can run the example using Jetty as follows:
+
+> mvn clean package jetty:run
+
+Go to the URL:
+
+<http://localhost:8080/bookstore-webapp/>
+
+#### Test Client Running
+
+> mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.external.ExternalTestContainerFactory -Djersey.config.test.logging.enable=true -Djersey.config.test.logging.dumpEntity=true -Djersey.config.test.container.port=8080 -Dmaven.test.skip=false
+
+or
+
+> mvn test -Prun-external-tests
+
+How it works
+------------
+
+This example shows how to support polymorphism of resources and JSP
+pages. Hence it is possible to add another resource, such as a DVD
+resource with associated JSP pages, which extends Item without having to
+change the logic of Bookstore or the existing JSP pages.
+
+JSP pages are associated with resource classes. Such JSP pages are
+resolved by converting the fully qualified class name of the resource
+class into a path and appending last path segment of the request URI
+path to that path. For example, when a GET is performed on the URI path
+"/" then the path to the JSP page is
+"/org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore/",
+and in this case since the last path segment is empty "index.jsp" is
+appended to the path. Then the request us forwarded to the JSP page at
+that path. Similarly when a GET is performed on the URI path "/count"
+then the path to the JSP page is
+"//org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore/count.jsp".
+
+The JSP variable "it" is set to the instance of Bookstore so that the
+index.jsp, or count.jsp, has access to the Bookstore instance as a Java
+bean.
+
+If a resource class inherits from another resource class then it will
+automatically inherit the JSPs from the super class.
+
+A JSP page may also include JSPs using the inheritance mechanism, for
+example the index.jsp page associated with the Book resource class
+includes a footer.jsp page whose location is specified by the super
+class, Item.
diff --git a/examples/bookstore-webapp/pom.xml b/examples/bookstore-webapp/pom.xml
new file mode 100644
index 0000000..8b910bb
--- /dev/null
+++ b/examples/bookstore-webapp/pom.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>bookstore-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-bookstore-webapp</name>
+
+ <description>Jersey MVC Bookstore example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-mvc-jsp</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>${servlet2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>jstl</artifactId>
+ <version>${jstl.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Run the application using "mvn jetty:run" -->
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>jetty-maven-plugin</artifactId>
+ <configuration>
+ <webApp>
+ <contextPath>/bookstore-webapp</contextPath>
+ </webApp>
+ <connectors>
+ <connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
+ <port>8080</port>
+ <responseHeaderSize>16192</responseHeaderSize>
+ </connector>
+ </connectors>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <!-- mvn test -Prun-external-tests -->
+ <id>run-external-tests</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <jersey.config.test.container.factory>${external.container.factory}</jersey.config.test.container.factory>
+ <jersey.config.test.container.port>${external.container.port}</jersey.config.test.container.port>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <properties>
+ <!-- External test container configuration is done via properties to allow overriding via command line. -->
+ <external.container.factory>org.glassfish.jersey.test.external.ExternalTestContainerFactory</external.container.factory>
+ <external.container.port>8080</external.container.port>
+ <maven.test.skip>false</maven.test.skip>
+ </properties>
+ </profile>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <properties>
+ <maven.test.skip>true</maven.test.skip>
+ </properties>
+
+</project>
diff --git a/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/MyApplication.java b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/MyApplication.java
new file mode 100644
index 0000000..fc591f6
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/MyApplication.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookstore.webapp;
+
+import org.glassfish.jersey.examples.bookstore.webapp.resource.Bookstore;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.TracingConfig;
+import org.glassfish.jersey.server.mvc.jsp.JspMvcFeature;
+
+/**
+ * @author Michal Gajdos
+ */
+public class MyApplication extends ResourceConfig {
+
+ public MyApplication() {
+ // Resources.
+ packages(Bookstore.class.getPackage().getName());
+
+ // MVC.
+ register(JspMvcFeature.class);
+
+ // Logging.
+ register(LoggingFeature.class);
+
+ // Tracing support.
+ property(ServerProperties.TRACING, TracingConfig.ON_DEMAND.name());
+ }
+}
diff --git a/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Book.java b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Book.java
new file mode 100644
index 0000000..4d0ff1b
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Book.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookstore.webapp.resource;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.FIELD)
+public class Book extends Item {
+
+ public Book() {
+ }
+
+ public Book(final String title, final String author) {
+ super(title, author);
+ }
+
+
+}
diff --git a/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore.java b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore.java
new file mode 100644
index 0000000..367eccb
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookstore.webapp.resource;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import javax.inject.Singleton;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.glassfish.jersey.server.mvc.Template;
+
+@Path("/")
+@Singleton
+@Template
+@Produces(MediaType.TEXT_HTML)
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.FIELD)
+public class Bookstore {
+
+ private final Map<String, Item> items = new TreeMap<>();
+ private String name;
+
+ public Bookstore() {
+ setName("Czech Bookstore");
+ getItems().put("1", new Book("Svejk", "Jaroslav Hasek"));
+ getItems().put("2", new Book("Krakatit", "Karel Capek"));
+ getItems().put("3", new CD("Ma Vlast 1", "Bedrich Smetana", new Track[] {
+ new Track("Vysehrad", 180),
+ new Track("Vltava", 172),
+ new Track("Sarka", 32)}));
+ }
+
+ @Path("items/{itemid}/")
+ public Item getItem(@PathParam("itemid") String itemid) {
+ Item i = getItems().get(itemid);
+ if (i == null) {
+ throw new NotFoundException(Response
+ .status(Response.Status.NOT_FOUND)
+ .entity("Item, " + itemid + ", is not found")
+ .build());
+ }
+
+ return i;
+ }
+
+ @GET
+ @Produces({MediaType.APPLICATION_XML, MediaType.TEXT_XML, MediaType.APPLICATION_JSON})
+ public Bookstore getXml() {
+ return this;
+ }
+
+ public long getSystemTime() {
+ return System.currentTimeMillis();
+ }
+
+ public Map<String, Item> getItems() {
+ return items;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/CD.java b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/CD.java
new file mode 100644
index 0000000..1e357e7
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/CD.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookstore.webapp.resource;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.FIELD)
+public class CD extends Item {
+
+ private Track[] tracks;
+
+ public CD() {
+ }
+
+ public CD(final String title, final String author, final Track[] tracks) {
+ super(title, author);
+ this.tracks = tracks;
+ }
+
+ public Track[] getTracks() {
+ return tracks;
+ }
+
+ @Path("tracks/{num}/")
+ public Track getTrack(@PathParam("num") int num) {
+ if (num >= tracks.length) {
+ throw new NotFoundException(Response
+ .status(Response.Status.NOT_FOUND)
+ .entity("Track, " + num + ", of CD, " + getTitle() + ", is not found")
+ .build());
+ }
+ return tracks[num];
+ }
+}
diff --git a/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Happy.java b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Happy.java
new file mode 100644
index 0000000..f9314d6
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Happy.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookstore.webapp.resource;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.server.mvc.Template;
+
+/**
+ * Generates a header display resource which is useful for displaying the
+ * various headers browsers use for making test cases
+ */
+@Template
+@Path("/happy")
+@Singleton
+public class Happy {
+
+ @Context
+ private HttpHeaders headers;
+
+ public String getHeaders() {
+ StringBuilder buf = new StringBuilder();
+ for (String header : headers.getRequestHeaders().keySet()) {
+ buf.append("<li>");
+ buf.append(header);
+ buf.append(" = ");
+ buf.append(headers.getRequestHeader(header));
+ buf.append("</li>\n");
+ }
+ return buf.toString();
+ }
+}
diff --git a/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Item.java b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Item.java
new file mode 100644
index 0000000..77bfbd9
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Item.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookstore.webapp.resource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.glassfish.jersey.server.mvc.Template;
+
+@Template
+@Produces(MediaType.TEXT_HTML)
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.FIELD)
+public class Item {
+
+ private String title;
+ private String author;
+
+ public Item() {
+ }
+
+ public Item(final String title, final String author) {
+ this.title = title;
+ this.author = author;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ @GET
+ @Produces({MediaType.APPLICATION_XML, MediaType.TEXT_XML, MediaType.APPLICATION_JSON})
+ public Item getXml() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "Item{"
+ + "title='" + title + '\''
+ + ", author='" + author + '\''
+ + '}';
+ }
+}
diff --git a/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Track.java b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Track.java
new file mode 100644
index 0000000..86888f1
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/java/org/glassfish/jersey/examples/bookstore/webapp/resource/Track.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookstore.webapp.resource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.glassfish.jersey.server.mvc.Template;
+
+@Template
+@Produces(MediaType.TEXT_HTML)
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.FIELD)
+public class Track {
+
+ private String name;
+ private int length;
+
+ public Track() {
+ }
+
+ public Track(String name, int length) {
+ this.name = name;
+ this.length = length;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ @GET
+ @Produces({MediaType.APPLICATION_XML, MediaType.TEXT_XML, MediaType.APPLICATION_JSON})
+ public Track getXml() {
+ return this;
+ }
+}
diff --git a/examples/bookstore-webapp/src/main/webapp/WEB-INF/web.xml b/examples/bookstore-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..1bba9e7
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+
+ <filter>
+ <filter-name>org.glassfish.jersey.examples.bookstore.webapp.MyApplication</filter-name>
+ <filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.glassfish.jersey.examples.bookstore.webapp.MyApplication</param-value>
+ </init-param>
+ <!-- pass to next filter if Jersey/App returns 404 -->
+ <init-param>
+ <param-name>jersey.config.servlet.filter.forwardOn404</param-name>
+ <param-value>true</param-value>
+ </init-param>
+ </filter>
+ <filter-mapping>
+ <filter-name>org.glassfish.jersey.examples.bookstore.webapp.MyApplication</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+</web-app>
diff --git a/examples/bookstore-webapp/src/main/webapp/css/style.css b/examples/bookstore-webapp/src/main/webapp/css/style.css
new file mode 100644
index 0000000..412a223
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/webapp/css/style.css
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+body {
+ font-family: Verdana, "Bitstream Vera Sans", sans-serif;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #111;
+}
+
+a {
+ color: #125AA7;
+}
+
+a:hover {
+ color: #000;
+}
+
+blockquote {
+ color: #666;
+}
+
+pre {
+ background: #eee;
+ border: 1px solid #ddd;
+}
+
+hr {
+ background: #B2CCFF;
+ color: #B2CCFF;
+}
+
+table {
+ border-top: 1px solid #ddd;
+ border-left: 1px solid #ddd;
+}
+
+th, td {
+ border-bottom: 1px solid #ddd;
+ border-right: 1px solid #ddd;
+}
+
+a img {
+ text-decoration: none;
+}
+
diff --git a/examples/bookstore-webapp/src/main/webapp/jsp/help.jsp b/examples/bookstore-webapp/src/main/webapp/jsp/help.jsp
new file mode 100644
index 0000000..8ac9f84
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/webapp/jsp/help.jsp
@@ -0,0 +1,26 @@
+<%--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@page contentType="text/html"%>
+<%@page pageEncoding="UTF-8"%>
+
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html>
+ <head>
+ <title></title>
+ </head>
+ <body>
+ Help web page
+ </body>
+</html>
diff --git a/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Book/index.jsp b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Book/index.jsp
new file mode 100644
index 0000000..5ec592e
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Book/index.jsp
@@ -0,0 +1,40 @@
+<%--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@page contentType="text/html"%>
+<%@page pageEncoding="UTF-8"%>
+<%--
+The taglib directive below imports the JSTL library. If you uncomment it,
+you must also add the JSTL library to the project. The Add Library... action
+on Libraries node in Projects view can be used to add the JSTL 1.1 library.
+--%>
+<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
+<%@taglib prefix="rbt" uri="urn:org:glassfish:jersey:servlet:mvc" %>
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+"http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Book</title>
+ </head>
+ <body>
+
+ <h1>${it.title}</h1>
+
+ Book from ${it.author}
+
+ <rbt:include page="footer.jsp"/>
+
+ </body>
+</html>
diff --git a/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore/count.jsp b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore/count.jsp
new file mode 100644
index 0000000..b6ff454
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore/count.jsp
@@ -0,0 +1,21 @@
+<%--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%--
+ An example of another side JSP.
+--%>
+<html>
+ <body>
+ # of items: ${fn:length(it.items)}
+ </body>
+</html>
diff --git a/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore/index.jsp b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore/index.jsp
new file mode 100644
index 0000000..21afccf
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore/index.jsp
@@ -0,0 +1,50 @@
+<%--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@page contentType="text/html"%>
+<%@page pageEncoding="UTF-8"%>
+
+<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <style type="text/css" media="screen">
+ @import url( <c:url value="/css/style.css"/> );
+ </style>
+ <title>REST Bookstore Sample</title>
+ </head>
+ <body>
+
+ <h1>${it.name}</h1>
+
+ <h2>Item List</h2>
+
+ <ul>
+ <c:forEach var="i" items="${it.items}">
+ <li><a href="items/${i.key}/">${i.value.title}</a>
+ </c:forEach>
+ </ul>
+
+ <h2>Others</h2>
+ <p>
+ <a href="count">count inventory</a>
+ <p>
+ <a href="time">get the system time</a>
+ <p>
+ <a href="jsp/help.jsp">regular resources</a>
+ </p>
+ </body>
+</html>
diff --git a/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore/time.jsp b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore/time.jsp
new file mode 100644
index 0000000..88ccced
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Bookstore/time.jsp
@@ -0,0 +1,18 @@
+<%--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<html>
+ <body>
+ Current system time is ${it.systemTime}
+ </body>
+</html>
diff --git a/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/CD/index.jsp b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/CD/index.jsp
new file mode 100644
index 0000000..6e6a896
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/CD/index.jsp
@@ -0,0 +1,44 @@
+<%--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@page contentType="text/html"%>
+<%@page pageEncoding="UTF-8"%>
+
+<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
+<%@taglib prefix="rbt" uri="urn:org:glassfish:jersey:servlet:mvc" %>
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>CD</title>
+ </head>
+ <body>
+
+ <h1>${it.title}</h1>
+
+ <p>CD from ${it.author}</p>
+
+ <h2>Track List</h2>
+
+ <ul>
+ <c:forEach var="t" items="${it.tracks}" varStatus="loop">
+ <li><a href="tracks/${loop.index}/">${t.name}</a></li>
+ </c:forEach>
+ </ul>
+
+ <rbt:include page="footer.jsp"/>
+
+ </body>
+</html>
diff --git a/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Happy/index.jsp b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Happy/index.jsp
new file mode 100644
index 0000000..feef3bf
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Happy/index.jsp
@@ -0,0 +1,36 @@
+<%--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@page contentType="text/html"%>
+<%@page pageEncoding="UTF-8"%>
+
+<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>REST Bookstore Happy Page!</title>
+ </head>
+ <body>
+
+ <h1>Happy Page!</h1>
+
+ <h2>Headers</h2>
+ <ul>
+ ${it.headers}
+ </ul>
+
+ </body>
+</html>
diff --git a/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Item/footer.jsp b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Item/footer.jsp
new file mode 100644
index 0000000..1cab567
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Item/footer.jsp
@@ -0,0 +1,18 @@
+<%--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<p>
+<a href="../../">Back</a>
+<hr>
+<div align="right">
+ Common footer (title ${it.title})
+</div>
diff --git a/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Track/index.jsp b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Track/index.jsp
new file mode 100644
index 0000000..e941f29
--- /dev/null
+++ b/examples/bookstore-webapp/src/main/webapp/org/glassfish/jersey/examples/bookstore/webapp/resource/Track/index.jsp
@@ -0,0 +1,35 @@
+<%--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@page contentType="text/html"%>
+<%@page pageEncoding="UTF-8"%>
+
+<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>CD Track</title>
+ </head>
+ <body>
+
+ <h1>${it.name}</h1>
+
+ <br>Track length: ${it.length} sec.
+
+ <p><a href="../../">back</a>
+
+ </body>
+</html>
diff --git a/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/BookstoreTest.java b/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/BookstoreTest.java
new file mode 100644
index 0000000..3e2d2cf
--- /dev/null
+++ b/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/BookstoreTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookstore.webapp.resource;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author James Strachan
+ * @author Naresh
+ * @author Michal Gajdos
+ */
+public class BookstoreTest extends TestSupport {
+
+ @Test
+ public void testResourceAsHtml() throws Exception {
+ assertBookstoreHtmlResponse(target("bookstore-webapp").request().get(String.class));
+ }
+
+ @Test
+ public void testHappyResourceAsHtml() throws Exception {
+ final String response = target("bookstore-webapp/happy").request().get(String.class);
+
+ assertHtmlResponse(response);
+ assertResponseContains(response, "Happy");
+ }
+
+ @Test
+ public void testResourceAsXml() throws Exception {
+ final Bookstore response = target("bookstore-webapp").request("application/xml").get(Bookstore.class);
+
+ assertNotNull("Should have returned a bookstore!", response);
+ assertEquals("bookstore name", "Czech Bookstore", response.getName());
+ }
+
+ @Test
+ public void testResourceAsHtmlUsingWebKitAcceptHeaders() throws Exception {
+ final String response = target("bookstore-webapp").request(
+ "text/html",
+ "application/xhtml+xml",
+ "application/xml;q=0.9",
+ "*/*;q=0.8").get(String.class);
+
+ assertBookstoreHtmlResponse(response);
+ }
+
+ protected void assertBookstoreHtmlResponse(String response) {
+ assertHtmlResponse(response);
+ assertResponseContains(response, "Bookstore");
+ assertResponseContains(response, "Item List");
+ }
+}
diff --git a/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/ItemTest.java b/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/ItemTest.java
new file mode 100644
index 0000000..0e14ab4
--- /dev/null
+++ b/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/ItemTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookstore.webapp.resource;
+
+import javax.ws.rs.client.WebTarget;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author James Strachan
+ * @author Naresh
+ * @author Michal Gajdos
+ */
+public class ItemTest extends TestSupport {
+
+ @Test
+ public void testResourceAsHtml() throws Exception {
+ final String response = item1resource().request().get(String.class);
+ assertItemHtmlResponse(response);
+ }
+
+ @Test
+ public void testResourceAsXml() throws Exception {
+ final String text = item1resource().request("application/xml").get(String.class);
+ System.out.println("Item XML is: " + text);
+
+ final Book response = item1resource().request("application/xml").get(Book.class);
+ assertNotNull("Should have returned an item!", response);
+ assertEquals("item title", "Svejk", response.getTitle());
+ }
+
+ @Test
+ public void testResourceAsHtmlUsingWebKitAcceptHeaders() throws Exception {
+ final String response = item1resource().request(
+ "text/html",
+ "application/xhtml+xml",
+ "application/xml;q=0.9",
+ "*/*;q=0.8").get(String.class);
+
+ assertItemHtmlResponse(response);
+ }
+
+ protected void assertItemHtmlResponse(String response) {
+ assertHtmlResponse(response);
+ assertResponseContains(response, "<title>Book</title>");
+ assertResponseContains(response, "<h1>Svejk</h1>");
+ }
+
+ protected WebTarget item1resource() {
+ return target("bookstore-webapp").path("/items/1");
+ }
+
+}
diff --git a/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/TestSupport.java b/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/TestSupport.java
new file mode 100644
index 0000000..f454e85
--- /dev/null
+++ b/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/TestSupport.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.bookstore.webapp.resource;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.examples.bookstore.webapp.MyApplication;
+import org.glassfish.jersey.server.mvc.jsp.JspMvcFeature;
+import org.glassfish.jersey.servlet.ServletProperties;
+import org.glassfish.jersey.test.JerseyTest;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author James Strachan
+ * @author Naresh
+ * @author Michal Gajdos
+ */
+public abstract class TestSupport extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ final MyApplication application = new MyApplication();
+ application.register(JspMvcFeature.class);
+ application.property(ServletProperties.FILTER_FORWARD_ON_404, true);
+ return application;
+ }
+
+ protected void assertHtmlResponse(String response) {
+ assertNotNull("No text returned!", response);
+
+ assertResponseContains(response, "<html>");
+ assertResponseContains(response, "</html>");
+ }
+
+ protected void assertResponseContains(String response, String text) {
+ assertTrue("Response should contain " + text + " but was: " + response, response.contains(text));
+ }
+}
diff --git a/examples/build.readme b/examples/build.readme
new file mode 100644
index 0000000..dc8b3b3
--- /dev/null
+++ b/examples/build.readme
@@ -0,0 +1,42 @@
+This read-me file briefly summarizes options for building Jersey examples.
+
+To build examples without generating individual example project source ZIP
+bundles, run:
+
+ > mvn clean install
+
+
+To build examples and generate individual example project source ZIP bundles,
+run the build with "examples-source-zip" profile enabled:
+
+ > mvn clean install -P release
+
+
+Some examples contain tests that depend on external container to be up and
+running. Tests in these examples are disabled by default. To enable the tests
+to be run against a properly set-up external container version, run the build
+with "run-external-tests" profile enabled:
+
+ > mvn clean install -P run-external-tests
+
+When running the build with the "run-external-tests" profile, following command
+line properties can be used to customize the build:
+
+ external.container.factory
+
+ - defines external test container factory class to be used for the tests
+ executed as part of the "run-external-tests" profile
+
+ - defaults to "org.glassfish.jersey.test.external.ExternalTestContainerFactory"
+
+
+ external.container.port
+
+ - defines the TCP/IP port of the external test container to be used for
+ the tests executed as part of the "run-external-tests" profile
+
+ - defaults to "8080"
+
+For example:
+
+ > mvn clean install -P run-external-tests -Dexternal.container.port=9090
diff --git a/examples/cdi-webapp/README.MD b/examples/cdi-webapp/README.MD
new file mode 100644
index 0000000..763a299
--- /dev/null
+++ b/examples/cdi-webapp/README.MD
@@ -0,0 +1,62 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Jersey CDI Integration CDI Beans WebApp Example
+===============================================
+
+This example demonstrates how to develop RESTful web service with CDI
+managed beans and a Servlet 3.0 Web container.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Description | Expected Results
+------------------------------------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------
+**_/cdi-webapp/helloworld_** | A managed bean with no use of injection whatsoever | Hello World
+**_/cdi-webapp/singleton_** | Shows injection of context objects into the fields of a managed bean. | OK GET /cdi-webapp/singleton
+**_/cdi-webapp/singleton/counter_** | Shows injection of context objects into the fields of a managed bean. | standalone Grizzly (no resource injection support) - 0 initial increment value, that gets incremented with each other request
+ | | Java EE compliant AS - 42 initial increment value
+**_/cdi-webapp/other/C/D_** | Shows injection of context objects and path parameters into the fields of a managed bean. | INTERCEPTED: OK GET /cdi-webapp/other, c=C, d=D
+**_/cdi-webapp/echofield/b?a=a_** | Shows injection of path and query parameters into the fields of a managed bean. | ECHO a b
+**_/cdi-webapp/echo/a_** | A managed bean that uses (but does not inject) a path parameter. | ECHO a
+
+Running the Example
+-------------------
+
+This example should work on any Java EE 7 compliant application server.
+It has been tested on a standalone GlassFish 4 instance. The easiest way
+to get the application running there is to build it and deploy as
+follows:
+
+ mvn clean package
+ $AS_HOME/asadmin deploy target/cdi-webapp.war
+
+Another option, introduced in Jersey 2.15, is to run this example in
+Grizzly HTTP server. To get the application running there you just
+invoke the following command:
+
+ mvn clean compile exec:java
+
+Since Weld Servlet support is provided also for Apache Tomcat server,
+there is yet another way how to deploy the application. Use the
+`tomcat-packaging` maven profile to get the WAR archive packaged in a
+way that makes it ready for Tomcat 7+ deployment:
+
+ mvn -Ptomcat-packaging clean package
+ cp target/cdi-webapp.war $CATALINA_HOME/webapps
+
+After you successfully deploy the application, visit the following URLs:
+
+- <http://localhost:8080/cdi-webapp/helloworld>
+- <http://localhost:8080/cdi-webapp/singleton>
+- <http://localhost:8080/cdi-webapp/singleton/counter>
+- <http://localhost:8080/cdi-webapp/other/C/D>
+- <http://localhost:8080/cdi-webapp/echofield/b?a=a>
+- <http://localhost:8080/cdi-webapp/echo/a>
\ No newline at end of file
diff --git a/examples/cdi-webapp/pom.xml b/examples/cdi-webapp/pom.xml
new file mode 100644
index 0000000..078e6b0
--- /dev/null
+++ b/examples/cdi-webapp/pom.xml
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>cdi-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-cdi-webapp</name>
+
+ <description>Jersey CDI example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.enterprise</groupId>
+ <artifactId>cdi-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency> <!-- this is to avoid Jersey jars to be bundled with the WAR -->
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.cdi</groupId>
+ <artifactId>jersey-weld2-se</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.cdi</groupId>
+ <artifactId>jersey-cdi1x</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <profiles>
+ <profile>
+ <id>run-external-tests</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <jersey.config.test.container.factory>${external.container.factory}</jersey.config.test.container.factory>
+ <jersey.config.test.container.port>${external.container.port}</jersey.config.test.container.port>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <properties>
+ <!-- External test container configuration is done via properties to allow overriding via command line. -->
+ <external.container.factory>org.glassfish.jersey.test.external.ExternalTestContainerFactory</external.container.factory>
+ <external.container.port>8080</external.container.port>
+ <maven.test.skip>false</maven.test.skip>
+ </properties>
+ </profile>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ <profile>
+ <id>tomcat-packaging</id>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.cdi</groupId>
+ <artifactId>jersey-cdi1x</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.cdi</groupId>
+ <artifactId>jersey-cdi1x-servlet</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.weld.servlet</groupId>
+ <artifactId>weld-servlet</artifactId>
+ </dependency>
+ </dependencies>
+ </profile>
+ </profiles>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.cdi.App</mainClass>
+ <classpathScope>test</classpathScope>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/App.java b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/App.java
new file mode 100644
index 0000000..1d970b9
--- /dev/null
+++ b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/App.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi;
+
+import java.io.IOException;
+
+import java.net.URI;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.jboss.weld.environment.se.Weld;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.jersey.examples.cdi.resources.MyApplication;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * Hello world!
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/cdi-webapp/");
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("Jersey CDI Example App");
+
+ final Weld weld = new Weld();
+ weld.initialize();
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, createJaxRsApp(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ weld.shutdown();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.\nTry out %s%s\nStop the application using CTRL+C",
+ BASE_URI, "application.wadl"));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ public static ResourceConfig createJaxRsApp() {
+ return new ResourceConfig(new MyApplication().getClasses());
+ }
+}
diff --git a/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/EchoParamConstructorResource.java b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/EchoParamConstructorResource.java
new file mode 100644
index 0000000..4efde86
--- /dev/null
+++ b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/EchoParamConstructorResource.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import java.util.logging.Logger;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import javax.annotation.ManagedBean;
+import javax.annotation.PostConstruct;
+
+import javax.inject.Inject;
+
+/**
+ * Shows constructor injection of a path parameter in a managed bean.
+ *
+ * @author Roberto Chinnici
+ */
+@ManagedBean
+@Path("echoparamconstructor/{a}")
+public class EchoParamConstructorResource {
+
+ static final Logger LOGGER = Logger.getLogger(EchoParamConstructorResource.class.getName());
+
+ String a;
+
+ // no-arg ctor is required by WLS
+ public EchoParamConstructorResource() {
+ }
+
+ @Inject
+ public EchoParamConstructorResource(@PathParam("a") String a) {
+ this.a = a;
+ }
+
+ @PostConstruct
+ @SuppressWarnings("unused")
+ private void postConstruct() {
+ LOGGER.info(String.format("in post construct, a=%s", a));
+ }
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String get() {
+ return "ECHO " + a;
+ }
+}
diff --git a/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/EchoParamFieldResource.java b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/EchoParamFieldResource.java
new file mode 100644
index 0000000..0526731
--- /dev/null
+++ b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/EchoParamFieldResource.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+
+import javax.annotation.ManagedBean;
+import javax.annotation.PostConstruct;
+
+/**
+ * Shows injection of path and query parameter into a managed bean.
+ *
+ * @author Roberto Chinnici
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@ManagedBean
+@Path("echofield/{b}")
+public class EchoParamFieldResource {
+
+ @PathParam("b") String bInjected;
+
+ String b;
+
+ /**
+ * Ensure we got path parameter value injected.
+ */
+ @PostConstruct
+ @SuppressWarnings("unused")
+ private void postConstruct() {
+ if (bInjected == null) {
+ throw new IllegalStateException("Field b has not been injected!");
+ }
+ b = bInjected;
+ }
+
+ /**
+ * Return a string containing injected values.
+ *
+ * @param a value of a query parameter a.
+ * @return message containing injected values.
+ */
+ @GET
+ @Produces("text/plain")
+ public String get(@QueryParam("a") String a) {
+ return String.format("ECHO %s %s", a, b);
+ }
+}
diff --git a/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/EchoParamResource.java b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/EchoParamResource.java
new file mode 100644
index 0000000..6e02543
--- /dev/null
+++ b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/EchoParamResource.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+
+import javax.annotation.ManagedBean;
+
+/**
+ * A managed bean that uses (but does not inject) a path parameter.
+ *
+ * @author Roberto Chinnici
+ */
+@ManagedBean
+@Path("echo/{a}")
+public class EchoParamResource {
+
+ @GET
+ @Produces("text/plain")
+ public String get(@PathParam("a") String param) {
+ return "ECHO " + param;
+ }
+}
diff --git a/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/HelloWorldResource.java b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/HelloWorldResource.java
new file mode 100644
index 0000000..f2210ab
--- /dev/null
+++ b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/HelloWorldResource.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.Path;
+
+import javax.annotation.ManagedBean;
+
+/**
+ * A managed bean with no use of injection whatsoever.
+ *
+ * @author Roberto Chinnici.
+ */
+@ManagedBean
+@Path("/helloworld")
+public class HelloWorldResource {
+
+ @GET
+ @Produces("text/plain")
+ public String getClichedMessage() {
+ return "Hello World";
+ }
+}
diff --git a/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/MyApplication.java b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/MyApplication.java
new file mode 100644
index 0000000..608799b
--- /dev/null
+++ b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/MyApplication.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+/**
+ * JAX-RS application.
+ *
+ * @author Jonathan Benoit (jonathan.benoit at oracle.com)
+ */
+@ApplicationPath("/*")
+public class MyApplication extends Application {
+ @Override
+ public Set<Class<?>> getClasses() {
+ final Set<Class<?>> classes = new HashSet<>();
+ classes.add(MySingletonResource.class);
+ classes.add(MyOtherResource.class);
+ classes.add(HelloWorldResource.class);
+ classes.add(EchoParamResource.class);
+ classes.add(EchoParamFieldResource.class);
+ classes.add(EchoParamConstructorResource.class);
+ classes.add(ProxyInjectedAppScopedResource.class);
+ classes.add(RequestScopedResource.class);
+ return classes;
+ }
+}
diff --git a/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/MyOtherResource.java b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/MyOtherResource.java
new file mode 100644
index 0000000..1bbb829
--- /dev/null
+++ b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/MyOtherResource.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.UriInfo;
+
+import javax.annotation.ManagedBean;
+
+import javax.enterprise.context.RequestScoped;
+
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptors;
+import javax.interceptor.InvocationContext;
+
+/**
+ * Shows injection of context objects and path parameters into the fields of a managed bean.
+ *
+ * @author Roberto Chinnici
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@ManagedBean
+@RequestScoped
+@Path("/other/{c}/{d}")
+public class MyOtherResource {
+
+ public static class MyInterceptor {
+
+ @AroundInvoke
+ public Object around(InvocationContext ctx) throws Exception {
+ return String.format("INTERCEPTED: %s", ctx.proceed());
+ }
+ }
+
+ @Context UriInfo uriInfo;
+ @Context Request request;
+
+ @PathParam("c") String c;
+ @PathParam("d") String d;
+
+ @GET
+ @Produces("text/plain")
+ @Interceptors(MyInterceptor.class)
+ public String get() {
+ return String.format("OK %s %s, c=%s, d=%s", request.getMethod(), uriInfo.getRequestUri(), c, d);
+ }
+}
diff --git a/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/MySingletonResource.java b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/MySingletonResource.java
new file mode 100644
index 0000000..eb6c410
--- /dev/null
+++ b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/MySingletonResource.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+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.core.Request;
+import javax.ws.rs.core.UriInfo;
+
+import javax.annotation.ManagedBean;
+import javax.annotation.Resource;
+import javax.enterprise.context.ApplicationScoped;
+
+/**
+ * Shows injection of context objects into the fields of a managed bean.
+ *
+ * @author Roberto Chinnici
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@ManagedBean
+@ApplicationScoped
+@Path("/singleton")
+public class MySingletonResource {
+
+ @Context
+ UriInfo uriInfo;
+ @Context
+ Request request;
+
+ @Resource(name = "injectedResource")
+ int counter = 0;
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String get() {
+ return String.format("OK %s uri=%s",
+ request.getMethod(), uriInfo.getRequestUri());
+ }
+
+ @Path("counter")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public synchronized String getCount() {
+ return String.format("%d", counter++);
+ }
+
+ @Path("counter")
+ @PUT
+ @Consumes(MediaType.TEXT_PLAIN)
+ public synchronized void setCount(String c) {
+ counter = Integer.decode(c);
+ }
+}
diff --git a/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/ProxyInjectedAppScopedResource.java b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/ProxyInjectedAppScopedResource.java
new file mode 100644
index 0000000..7b1eebe
--- /dev/null
+++ b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/ProxyInjectedAppScopedResource.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * Application scoped CDI bean to demonstrate a dynamic proxy is being injected
+ * for URI info so that actual request information is available.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oralcle.com)
+ */
+@ApplicationScoped
+@Path("ui-app")
+public class ProxyInjectedAppScopedResource {
+
+ @Context UriInfo uiField;
+
+ @Path("{p}")
+ @GET
+ public String getUri(@Context UriInfo uiParam) {
+ if (uiParam == uiField) {
+ throw new IllegalStateException("Dynamic proxy expected in the uiField");
+ }
+ return uiField.getRequestUri().getPath();
+ }
+}
diff --git a/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/RequestScopedResource.java b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/RequestScopedResource.java
new file mode 100644
index 0000000..5c127f3
--- /dev/null
+++ b/examples/cdi-webapp/src/main/java/org/glassfish/jersey/examples/cdi/resources/RequestScopedResource.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.RequestScoped;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * Request scoped CDI bean to demonstrate no dynamic proxy is being injected
+ * for JAX-RS request scoped URI info.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oralcle.com)
+ */
+@RequestScoped
+@Path("ui-req")
+public class RequestScopedResource {
+
+ @Context UriInfo uiField;
+
+ @Path("{p}")
+ @GET
+ public String getUri(@Context UriInfo uiParam) {
+ if (uiParam != uiField) {
+ throw new IllegalStateException("No dynamic proxy expected in the uiField");
+ }
+ return uiField.getRequestUri().getPath();
+ }
+}
diff --git a/examples/cdi-webapp/src/main/resources/META-INF/beans.xml b/examples/cdi-webapp/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000..5d361e9
--- /dev/null
+++ b/examples/cdi-webapp/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<beans/>
diff --git a/examples/cdi-webapp/src/main/webapp/WEB-INF/beans.xml b/examples/cdi-webapp/src/main/webapp/WEB-INF/beans.xml
new file mode 100644
index 0000000..5d361e9
--- /dev/null
+++ b/examples/cdi-webapp/src/main/webapp/WEB-INF/beans.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<beans/>
diff --git a/examples/cdi-webapp/src/main/webapp/WEB-INF/web.xml b/examples/cdi-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..3090928
--- /dev/null
+++ b/examples/cdi-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+ <env-entry>
+ <env-entry-name>injectedResource</env-entry-name>
+ <env-entry-type>java.lang.Integer</env-entry-type>
+ <env-entry-value>42</env-entry-value>
+ </env-entry>
+</web-app>
diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/CdiTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/CdiTest.java
new file mode 100644
index 0000000..e85295e
--- /dev/null
+++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/CdiTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import java.net.URI;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.inject.hk2.Hk2InjectionManagerFactory;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.jboss.weld.environment.se.Weld;
+import org.junit.Assume;
+import org.junit.Before;
+
+/**
+ * Test for CDI web application resources.
+ * Run with:
+ * <pre>
+ * mvn clean package
+ * $AS_HOME/bin/asadmin deploy target/cdi-webapp
+ * mvn -DskipTests=false test</pre>
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class CdiTest extends JerseyTest {
+
+ Weld weld;
+
+ @Before
+ public void setup() {
+ Assume.assumeTrue(Hk2InjectionManagerFactory.isImmediateStrategy());
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ weld = new Weld();
+ weld.initialize();
+ super.setUp();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ weld.shutdown();
+ super.tearDown();
+ }
+
+ @Override
+ protected Application configure() {
+ return new MyApplication();
+ }
+
+ @Override
+ protected URI getBaseUri() {
+ return UriBuilder.fromUri(super.getBaseUri()).path("cdi-webapp").build();
+ }
+}
diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoParamBeanTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoParamBeanTest.java
new file mode 100644
index 0000000..619671a
--- /dev/null
+++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoParamBeanTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.ws.rs.client.WebTarget;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Test for the echo with injected param managed bean resource.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@RunWith(Parameterized.class)
+public class EchoParamBeanTest extends CdiTest {
+
+ @Parameterized.Parameters
+ public static List<Object[]> testData() {
+ return Arrays.asList(new Object[][] {
+ {"alpha", "beta"},
+ {"AAA", "BBB"},
+ {"b", "a"},
+ {"1$s", "2&d"}
+ });
+ }
+
+ final String a, b;
+
+ /**
+ * Create a new test case based on the above defined parameters.
+ *
+ * @param a query parameter value
+ * @param b path parameter value
+ */
+ public EchoParamBeanTest(String a, String b) {
+ this.a = a;
+ this.b = b;
+ }
+
+ @Test
+ public void testEchoParamResource() {
+
+ final WebTarget target = target().path("echofield").path(b);
+
+ String s = target.queryParam("a", a).request().get(String.class);
+
+ assertThat(s, containsString("ECHO"));
+ assertThat(s, containsString(String.format("%s %s", a, b)));
+ }
+}
diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoResourceTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoResourceTest.java
new file mode 100644
index 0000000..90664a7
--- /dev/null
+++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoResourceTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.ws.rs.client.WebTarget;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Test for the echo resource.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@RunWith(Parameterized.class)
+public class EchoResourceTest extends CdiTest {
+
+ @Parameterized.Parameters
+ public static List<Object[]> testData() {
+ return Arrays.asList(new Object[][] {
+ {"alpha"},
+ {"AAA"},
+ {"b"},
+ {"1"}
+ });
+ }
+
+ final String a;
+
+ /**
+ * Create a new test case based on the above defined parameters.
+ *
+ * @param a path parameter value
+ */
+ public EchoResourceTest(String a) {
+ this.a = a;
+ }
+
+ @Test
+ public void testEchoResource() {
+
+ final WebTarget target = target().path("echo").path(a);
+
+ String s = target.request().get(String.class);
+
+ assertThat(s, containsString("ECHO"));
+ assertThat(s, containsString(a));
+ }
+
+ @Test
+ public void testEchoParamCtorResource() {
+
+ final WebTarget target = target().path("echoparamconstructor").path(a);
+
+ String s = target.request().get(String.class);
+
+ assertThat(s, containsString("ECHO"));
+ assertThat(s, containsString(a));
+ }
+}
diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/HelloworldTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/HelloworldTest.java
new file mode 100644
index 0000000..4db5323
--- /dev/null
+++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/HelloworldTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.containsString;
+
+/**
+ * Test for the helloworld resource.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class HelloworldTest extends CdiTest {
+
+ @Test
+ public void testHelloworldResource() {
+ String s = target().path("helloworld").request().get(String.class);
+ assertThat(s, containsString("Hello World"));
+ }
+}
diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerApplicationBeanTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerApplicationBeanTest.java
new file mode 100644
index 0000000..f3ec4d2
--- /dev/null
+++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerApplicationBeanTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.containsString;
+
+/**
+ * Test for the application scoped managed bean resource.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class PerApplicationBeanTest extends CdiTest {
+
+ @Test
+ public void testApplicationScopedResource() {
+
+ final WebTarget singleton = target().path("singleton");
+
+ String s = singleton.request().get(String.class);
+ assertThat(s, containsString(singleton.getUri().toString()));
+ assertThat(s, containsString("GET"));
+
+ final WebTarget counter = singleton.path("counter");
+
+ // TODO: JERSEY-2744:
+ // TODO: @Resource injection will not work on SE
+ // TODO: add a custom extension to make this work with Grizzly
+// String c42 = counter.request().get(String.class);
+// assertThat(c42, containsString("42"));
+//
+// String c43 = counter.request().get(String.class);
+// assertThat(c43, containsString("43"));
+
+ counter.request().put(Entity.text("12"));
+
+ String c12 = counter.request().get(String.class);
+ assertThat(c12, containsString("12"));
+
+ counter.request().put(Entity.text("42"));
+ }
+}
diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerRequestBeanTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerRequestBeanTest.java
new file mode 100644
index 0000000..1532cc6
--- /dev/null
+++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerRequestBeanTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.ws.rs.client.WebTarget;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Test for the request scoped managed bean resource.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@RunWith(Parameterized.class)
+public class PerRequestBeanTest extends CdiTest {
+
+ @Parameterized.Parameters
+ public static List<Object[]> testData() {
+ return Arrays.asList(new Object[][] {
+ {"gamma", "delta"},
+ {"CC C", "D DD"},
+ {"d", "c"},
+ {"@^&", "?!:"}
+ });
+ }
+
+ final String c, d;
+
+ /**
+ * Create a new test case based on the above defined parameters.
+ *
+ * @param c first path parameter value.
+ * @param d second path parameter value.
+ */
+ public PerRequestBeanTest(String c, String d) {
+ this.c = c;
+ this.d = d;
+ }
+
+ @Test
+ public void testTheOtherResource() {
+ final WebTarget target = target().path("other").path(c).path(d);
+
+ String s = target.request().get(String.class);
+ assertThat(s, containsString(target.getUri().toString()));
+ assertThat(s, containsString("GET"));
+ assertThat(s, containsString(String.format("c=%s", c)));
+ assertThat(s, containsString(String.format("d=%s", d)));
+ assertThat(s, startsWith("INTERCEPTED"));
+ }
+}
diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/ProxyScopeAlignmentTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/ProxyScopeAlignmentTest.java
new file mode 100644
index 0000000..b963e3d
--- /dev/null
+++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/ProxyScopeAlignmentTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.cdi.resources;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.ws.rs.client.WebTarget;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Ensure CDI and JAX-RS scopes are well aligned, so that dynamic proxies
+ * are only created when needed.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@RunWith(Parameterized.class)
+public class ProxyScopeAlignmentTest extends CdiTest {
+
+ @Parameterized.Parameters
+ public static List<Object[]> testData() {
+ return Arrays.asList(new Object[][] {
+ {"one"},
+ {"too"},
+ {"much"}
+ });
+ }
+
+ final String p;
+
+ /**
+ * Create a new test case based on the above defined parameters.
+ *
+ * @param p path parameter value
+ */
+ public ProxyScopeAlignmentTest(String p) {
+ this.p = p;
+ }
+
+ @Test
+ public void testUiInjection() {
+
+ final WebTarget app = target().path("ui-app").path(p);
+ final WebTarget req = target().path("ui-req").path(p);
+
+ String ar = app.request().get(String.class);
+ String rr = req.request().get(String.class);
+
+ assertThat(ar, containsString(p));
+ assertThat(rr, containsString(p));
+ }
+}
diff --git a/examples/clipboard-programmatic/README.MD b/examples/clipboard-programmatic/README.MD
new file mode 100644
index 0000000..e3f096d
--- /dev/null
+++ b/examples/clipboard-programmatic/README.MD
@@ -0,0 +1,45 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Clipboard Example
+=================
+
+Very basic programmatically created resource example showcases CRUD functionality
+implemented via HTTP POST, GET, PUT and DELETE methods.
+A simple clipboard is simulated which is capable of handling text data only.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+------------------------- | ---------------- | --------------
+**_/clipboard_** | programmatically | GET
+**_/clipboard_** | programmatically | POST
+**_/clipboard_** | programmatically | PUT
+**_/clipboard_** | programmatically | DELETE
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the Clipboard example using [Grizzly](http://grizzly.java.net/)
+
+You can post any text:
+
+```
+curl -v -X POST http://localhost:8080/base/clipboard -H "Content-Type:text/plain" -d 'The urgent task'
+```
+
+After you successfully deploy the application and post entities, visit the following URLs:
+
+- <http://localhost:8080/base/clipboard>
\ No newline at end of file
diff --git a/examples/clipboard-programmatic/pom.xml b/examples/clipboard-programmatic/pom.xml
new file mode 100644
index 0000000..f1321db
--- /dev/null
+++ b/examples/clipboard-programmatic/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>clipboard-programmatic</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-clipboard-programmatic</name>
+
+ <description>Jersey programmatic resource API clipboard example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.clipboard.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/clipboard-programmatic/src/main/java/org/glassfish/jersey/examples/clipboard/App.java b/examples/clipboard-programmatic/src/main/java/org/glassfish/jersey/examples/clipboard/App.java
new file mode 100644
index 0000000..e96397f
--- /dev/null
+++ b/examples/clipboard-programmatic/src/main/java/org/glassfish/jersey/examples/clipboard/App.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.clipboard;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.process.Inflector;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.model.Resource;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * This is the example entry point, where Jersey application for the example
+ * gets populated and published using the Grizzly 2 HTTP container.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/base/");
+ /**
+ * Clipboard root resource path.
+ */
+ public static final String ROOT_PATH = "clipboard";
+
+ /**
+ * Main application entry point.
+ *
+ * @param args application arguments.
+ */
+ public static void main(String[] args) {
+ try {
+ System.out.println("Clipboard Jersey Example App");
+
+ final ResourceConfig config = createProgrammaticClipboardApp();
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, config, false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(
+ String.format("Application started.%n"
+ + "Try out %s%s%n"
+ + "Stop the application using CTRL+C",
+ BASE_URI, ROOT_PATH));
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ /**
+ * Create example application resource configuration.
+ *
+ * @return initialized resource configuration of the example application.
+ */
+ public static ResourceConfig createProgrammaticClipboardApp() {
+ final Resource.Builder resourceBuilder = Resource.builder(ROOT_PATH);
+ final Clipboard clipboard = new Clipboard();
+
+ resourceBuilder.addMethod("GET").handledBy(new Inflector<ContainerRequestContext, Response>() {
+
+ @Override
+ public Response apply(ContainerRequestContext data) {
+ final String content = clipboard.content();
+ if (content.isEmpty()) {
+ return Response.noContent().build();
+ }
+ return Response.ok(content).build();
+ }
+ });
+
+ resourceBuilder.addMethod("PUT").handledBy(new Inflector<ContainerRequestContext, Response>() {
+
+ @Override
+ public Response apply(ContainerRequestContext data) {
+ if (data != null) {
+ clipboard.setContent(((ContainerRequest) data).readEntity(String.class));
+ }
+ return Response.noContent().build();
+ }
+ });
+
+ resourceBuilder.addMethod("POST").handledBy(new Inflector<ContainerRequestContext, Response>() {
+
+ @Override
+ public Response apply(ContainerRequestContext data) {
+ String newContent = (data != null)
+ ? clipboard.append(((ContainerRequest) data).readEntity(String.class)) : "";
+ return Response.ok(newContent).build();
+ }
+ });
+
+ resourceBuilder.addMethod("DELETE").handledBy(new Inflector<ContainerRequestContext, Response>() {
+
+ @Override
+ public Response apply(ContainerRequestContext data) {
+ clipboard.clear();
+ return Response.noContent().build();
+ }
+ });
+
+ return new ResourceConfig().registerResources(resourceBuilder.build());
+ }
+}
diff --git a/examples/clipboard-programmatic/src/main/java/org/glassfish/jersey/examples/clipboard/Clipboard.java b/examples/clipboard-programmatic/src/main/java/org/glassfish/jersey/examples/clipboard/Clipboard.java
new file mode 100644
index 0000000..2788c72
--- /dev/null
+++ b/examples/clipboard-programmatic/src/main/java/org/glassfish/jersey/examples/clipboard/Clipboard.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.clipboard;
+
+/**
+ * Simple clipboard implementation.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class Clipboard {
+ private final StringBuffer content = new StringBuffer();
+
+ public String content() { // GET
+ return content.toString();
+ }
+
+ public void setContent(String replacement) { // PUT
+ content.delete(0, content.length()).append(replacement);
+ }
+
+ public String append(String append) { // POST
+ return content.append(append).toString();
+ }
+
+ public void clear() { // DELETE
+ content.delete(0, content.length());
+ }
+
+}
diff --git a/examples/clipboard-programmatic/src/test/java/org/glassfish/jersey/examples/clipboard/ClipboardTest.java b/examples/clipboard-programmatic/src/test/java/org/glassfish/jersey/examples/clipboard/ClipboardTest.java
new file mode 100644
index 0000000..c708156
--- /dev/null
+++ b/examples/clipboard-programmatic/src/test/java/org/glassfish/jersey/examples/clipboard/ClipboardTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.clipboard;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class ClipboardTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ return App.createProgrammaticClipboardApp();
+ }
+
+ @Test
+ public void testClipboard() throws Exception {
+ final Client client = client();
+ final WebTarget clipboard = client.target(getBaseUri()).path(App.ROOT_PATH);
+
+ Response response;
+
+ response = clipboard.request("text/plain").get();
+ assertEquals(204, response.getStatus());
+
+ response = clipboard.request("text/plain").put(Entity.text("Hello"));
+ assertEquals(204, response.getStatus());
+ assertEquals("Hello", clipboard.request("text/plain").get(String.class));
+
+ response = clipboard.request("text/plain").post(Entity.text(" World!"));
+ assertEquals(200, response.getStatus());
+ assertEquals("Hello World!", clipboard.request("text/plain").get(String.class));
+
+ response = clipboard.request("text/plain").delete();
+ assertEquals(204, response.getStatus());
+ assertEquals(204, clipboard.request("text/plain").get().getStatus());
+ }
+}
diff --git a/examples/clipboard/README.MD b/examples/clipboard/README.MD
new file mode 100644
index 0000000..0f1df89
--- /dev/null
+++ b/examples/clipboard/README.MD
@@ -0,0 +1,57 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Clipboard Example
+=================
+
+Very basic resource example showcases CRUD functionality
+implemented via HTTP POST, GET, PUT and DELETE methods.
+A simple clipboard is simulated which is capable of handling text data only.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+------------------------- | ---------------- | --------------
+**_/clipboard_** | ClipboardResource | GET
+**_/clipboard_** | ClipboardResource | POST
+**_/clipboard_** | ClipboardResource | PUT
+**_/clipboard_** | ClipboardResource | DELETE
+**_/clipboard/history_** | ClipboardResource | GET
+**_/clipboard/history_** | ClipboardResource | DELETE
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the Clipboard example using [Grizzly](http://grizzly.java.net/)
+
+You can post a JSON entity:
+
+```
+curl -X POST http://localhost:8080/base/clipboard -H "Content-Type:application/json" -d '
+{
+ content: "The urgent task."
+}'
+```
+
+or plain text entity:
+
+```
+curl -v -X POST http://localhost:8080/base/clipboard -H "Content-Type:text/plain" -d 'The urgent task'
+```
+
+After you successfully deploy the application and post entities, visit the following URLs:
+
+- <http://localhost:8080/base/clipboard>
+- <http://localhost:8080/base/clipboard/history>
\ No newline at end of file
diff --git a/examples/clipboard/pom.xml b/examples/clipboard/pom.xml
new file mode 100644
index 0000000..1655949
--- /dev/null
+++ b/examples/clipboard/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>clipboard</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-clipboard</name>
+
+ <description>Jersey clipboard example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.clipboard.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/clipboard/src/main/java/org/glassfish/jersey/examples/clipboard/App.java b/examples/clipboard/src/main/java/org/glassfish/jersey/examples/clipboard/App.java
new file mode 100644
index 0000000..49aed24
--- /dev/null
+++ b/examples/clipboard/src/main/java/org/glassfish/jersey/examples/clipboard/App.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.clipboard;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.process.Inflector;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.model.Resource;
+import org.glassfish.jersey.server.model.ResourceMethod;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * This is the example entry point, where Jersey application for the example
+ * gets populated and published using the Grizzly 2 HTTP container.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/base/");
+ /**
+ * Clipboard root resource path.
+ */
+ public static final String ROOT_PATH = "clipboard";
+
+ /**
+ * Main application entry point.
+ *
+ * @param args application arguments.
+ */
+ public static void main(String[] args) {
+ try {
+ System.out.println("Clipboard Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, createApp(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(
+ String.format("Application started.%n"
+ + "Try out %s%s%n"
+ + "Stop the application using CTRL+C",
+ BASE_URI, ROOT_PATH));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Create example application resource configuration.
+ *
+ * @return initialized resource configuration of the example application.
+ */
+ public static ResourceConfig createApp() {
+
+ MediaType[] jsonAndTextTypes = new MediaType[] {MediaType.APPLICATION_JSON_TYPE, MediaType.TEXT_PLAIN_TYPE};
+
+ final ResourceConfig resourceConfig = new ResourceConfig(
+ ClipboardResource.class,
+ ClipboardDataProvider.ApplicationJson.class,
+ ClipboardDataProvider.TextPlain.class);
+
+ final Resource.Builder resourceBuilder = Resource.builder("echo");
+ ResourceMethod.Builder rmBuilder = resourceBuilder.addMethod("POST");
+ rmBuilder.consumes(jsonAndTextTypes).produces(jsonAndTextTypes)
+ .handledBy(new Inflector<ContainerRequestContext, Response>() {
+
+ @Override
+ public Response apply(ContainerRequestContext request) {
+ ClipboardData data = (request != null)
+ ? ((ContainerRequest) request).readEntity(ClipboardData.class) : null;
+ return Response.ok(data).build();
+ }
+ });
+
+ resourceConfig.registerResources(resourceBuilder.build());
+ return resourceConfig;
+ }
+}
diff --git a/examples/clipboard/src/main/java/org/glassfish/jersey/examples/clipboard/ClipboardData.java b/examples/clipboard/src/main/java/org/glassfish/jersey/examples/clipboard/ClipboardData.java
new file mode 100644
index 0000000..6b5b4d5
--- /dev/null
+++ b/examples/clipboard/src/main/java/org/glassfish/jersey/examples/clipboard/ClipboardData.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.clipboard;
+
+import java.util.Objects;
+
+/**
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class ClipboardData {
+
+ private String content;
+
+ public ClipboardData(String content) {
+ this.content = content;
+ }
+
+ @Override
+ public String toString() {
+ return content;
+ }
+
+ boolean isEmpty() {
+ return "".equals(content);
+ }
+
+ ClipboardData append(ClipboardData addition) {
+ content = content + addition.content;
+ return this;
+ }
+
+ void clear() {
+ content = "";
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ClipboardData)) {
+ return false;
+ }
+ final ClipboardData that = (ClipboardData) o;
+ return Objects.equals(content, that.content);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(content);
+ }
+}
diff --git a/examples/clipboard/src/main/java/org/glassfish/jersey/examples/clipboard/ClipboardDataProvider.java b/examples/clipboard/src/main/java/org/glassfish/jersey/examples/clipboard/ClipboardDataProvider.java
new file mode 100644
index 0000000..d24eb25
--- /dev/null
+++ b/examples/clipboard/src/main/java/org/glassfish/jersey/examples/clipboard/ClipboardDataProvider.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.clipboard;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.logging.Logger;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import org.glassfish.jersey.message.MessageUtils;
+
+/**
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public abstract class ClipboardDataProvider implements MessageBodyWriter, MessageBodyReader {
+
+ @Provider
+ @Consumes("text/plain")
+ @Produces("text/plain")
+ public static class TextPlain extends ClipboardDataProvider {
+
+ @Override
+ public void writeTo(final Object t, final Class type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType, final MultivaluedMap httpHeaders, final OutputStream entityStream)
+ throws IOException, WebApplicationException {
+ entityStream.write(t.toString().getBytes(MessageUtils.getCharset(mediaType)));
+ }
+
+ @Override
+ public Object readFrom(final Class type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType, final MultivaluedMap httpHeaders, final InputStream entityStream)
+ throws IOException, WebApplicationException {
+ return new ClipboardData(readStringFromStream(entityStream, MessageUtils.getCharset(mediaType)));
+ }
+ }
+
+ @Provider
+ @Consumes("application/json")
+ @Produces("application/json")
+ public static class ApplicationJson extends ClipboardDataProvider {
+
+ private static final String JsonOpenning = "{\"content\":\"";
+ private static final String JsonClosing = "\"}";
+
+ @Override
+ public void writeTo(final Object t, final Class type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType, final MultivaluedMap httpHeaders, final OutputStream entityStream)
+ throws IOException, WebApplicationException {
+ entityStream.write(String.format("%s%s%s", JsonOpenning, t.toString(), JsonClosing)
+ .getBytes(MessageUtils.getCharset(mediaType)));
+ }
+
+ @Override
+ public Object readFrom(final Class type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType, final MultivaluedMap httpHeaders, final InputStream entityStream)
+ throws IOException, WebApplicationException {
+ final String jsonExpression = readStringFromStream(entityStream, MessageUtils.getCharset(mediaType));
+ return new ClipboardData(jsonExpression.replace(JsonOpenning, "").replace(JsonClosing, ""));
+ }
+ }
+
+ @Override
+ public boolean isWriteable(final Class type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return isKnownType(type, genericType);
+ }
+
+ private boolean isKnownType(final Class<?> type, final Type genericType) {
+ return type.isAssignableFrom(ClipboardData.class)
+ || (Collection.class.isAssignableFrom(type)
+ && (((ParameterizedType) genericType).getActualTypeArguments()[0]).equals(String.class));
+ }
+
+ @Override
+ public long getSize(final Object t, final Class type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public boolean isReadable(final Class type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return isKnownType(type, genericType);
+ }
+
+ private static String readStringFromStream(final InputStream entityStream, Charset charset) throws IOException {
+ final StringBuilder result = new StringBuilder();
+ final byte[] buf = new byte[2048];
+ int i;
+ while ((i = entityStream.read(buf)) != -1) {
+ result.append(new String(buf, 0, i, charset));
+ }
+ return result.toString();
+ }
+}
diff --git a/examples/clipboard/src/main/java/org/glassfish/jersey/examples/clipboard/ClipboardResource.java b/examples/clipboard/src/main/java/org/glassfish/jersey/examples/clipboard/ClipboardResource.java
new file mode 100644
index 0000000..f167d5c
--- /dev/null
+++ b/examples/clipboard/src/main/java/org/glassfish/jersey/examples/clipboard/ClipboardResource.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.clipboard;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+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.core.Request;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Variant;
+
+/**
+ * Very basic resource example showcases CRUD functionality
+ * implemented via HTTP POST, GET, PUT and DELETE methods.
+ * A simple clipboard is simulated which is capable of handling
+ * text data only.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("clipboard")
+public class ClipboardResource {
+
+ static final List<Variant> supportedVariants =
+ Variant.mediaTypes(
+ MediaType.APPLICATION_JSON_TYPE,
+ MediaType.TEXT_PLAIN_TYPE).add().build();
+
+ private static final List<String> history = new LinkedList<String>();
+
+ private static ClipboardData content = new ClipboardData("");
+
+ @Context
+ Request request;
+
+ @GET
+ public Response content() {
+
+ if (content.isEmpty()) {
+ return Response.noContent().build();
+ }
+
+ final Variant variant = request.selectVariant(supportedVariants);
+
+ if (variant == null) {
+ return Response.notAcceptable(supportedVariants).build();
+ } else {
+ return Response.ok(content, variant.getMediaType()).build();
+ }
+ }
+
+ @PUT
+ @Consumes({"text/plain", "application/json"})
+ public void setContent(ClipboardData newContent) {
+ saveHistory();
+ updateContent(newContent);
+ }
+
+ private static void updateContent(ClipboardData newContent) {
+ content = newContent;
+ }
+
+ @POST
+ @Consumes({"text/plain", "application/json"})
+ @Produces({"text/plain", "application/json"})
+ public ClipboardData append(ClipboardData appendix) {
+ saveHistory();
+ return content.append(appendix);
+ }
+
+ @DELETE
+ public void clear() {
+ saveHistory();
+ content.clear();
+ }
+
+ @GET
+ @Path("history")
+ @Produces({"text/plain", "application/json"})
+ public List<String> getHistory() {
+ return history;
+ }
+
+ @DELETE
+ @Path("history")
+ public void clearHistory() {
+ history.clear();
+ }
+
+ private void saveHistory() {
+ String currentContent = content.toString();
+ if (!currentContent.isEmpty()) {
+ history.add(currentContent);
+ }
+ }
+}
diff --git a/examples/clipboard/src/test/java/org/glassfish/jersey/examples/clipboard/ClipboardTest.java b/examples/clipboard/src/test/java/org/glassfish/jersey/examples/clipboard/ClipboardTest.java
new file mode 100644
index 0000000..4fdb3b3
--- /dev/null
+++ b/examples/clipboard/src/test/java/org/glassfish/jersey/examples/clipboard/ClipboardTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.clipboard;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class ClipboardTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ return App.createApp();
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ client().target(getBaseUri()).path(App.ROOT_PATH).request().delete();
+ client().target(getBaseUri()).path(App.ROOT_PATH).path("history").request().delete();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(ClipboardDataProvider.ApplicationJson.class).register(ClipboardDataProvider.TextPlain.class);
+ }
+
+ @Test
+ public void testDeclarativeClipboardTextPlain() throws Exception {
+ testDeclarativeClipboard(MediaType.TEXT_PLAIN_TYPE);
+ }
+
+ @Test
+ public void testDeclarativeClipboardAppJson() throws Exception {
+ testDeclarativeClipboard(MediaType.APPLICATION_JSON_TYPE);
+ }
+
+ public void testDeclarativeClipboard(MediaType mediaType) throws Exception {
+ final WebTarget clipboard = client().target(getBaseUri()).path(App.ROOT_PATH);
+
+ Response response;
+
+ response = clipboard.request(mediaType).get();
+ assertEquals(204, response.getStatus());
+
+ response = clipboard.request(mediaType).put(Entity.entity(new ClipboardData("Hello"), mediaType));
+ assertEquals(204, response.getStatus());
+
+ assertEquals("Hello", clipboard.request(mediaType).get(ClipboardData.class).toString());
+
+ response = clipboard.request(mediaType).post(Entity.entity(new ClipboardData(" World!"), mediaType));
+ assertEquals(200, response.getStatus());
+
+ assertEquals("Hello World!", clipboard.request(mediaType).get(ClipboardData.class).toString());
+
+ response = clipboard.request(mediaType).delete();
+ assertEquals(204, response.getStatus());
+
+ assertEquals(204, clipboard.request(mediaType).get().getStatus());
+ }
+
+ @Test
+ public void testProgrammaticEchoTextPlain() throws Exception {
+ testProgrammaticEcho(MediaType.TEXT_PLAIN_TYPE);
+ }
+
+ @Test
+ public void testProgrammaticEchoAppJson() throws Exception {
+ testProgrammaticEcho(MediaType.APPLICATION_JSON_TYPE);
+ }
+
+ @Test
+ public void testHistoryInJson() throws Exception {
+ callGetHistory(MediaType.APPLICATION_JSON_TYPE);
+ }
+
+ @Test
+ public void testHistoryInTextPlain() throws Exception {
+ callGetHistory(MediaType.TEXT_PLAIN_TYPE);
+ }
+
+ private void callGetHistory(final MediaType mediaType) {
+ final WebTarget clipboard = client().target(getBaseUri()).path(App.ROOT_PATH);
+ clipboard.request(mediaType).post(Entity.entity(new ClipboardData("Task 1 "), mediaType));
+ clipboard.request(mediaType).post(Entity.entity(new ClipboardData("Task 2 "), mediaType));
+ clipboard.request(mediaType).post(Entity.entity(new ClipboardData("Task 3 "), mediaType));
+
+ ClipboardData response = clipboard.path("history").request(mediaType).get(ClipboardData.class);
+ assertEquals(new ClipboardData("[Task 1 , Task 1 Task 2 ]"), response);
+ }
+
+ public void testProgrammaticEcho(MediaType mediaType) throws Exception {
+ final WebTarget echo = client().target(getBaseUri()).path("echo");
+
+ Response response = echo.request(mediaType).post(Entity.entity(new ClipboardData("Hello"), mediaType));
+ assertEquals("Hello", response.readEntity(ClipboardData.class).toString());
+ }
+}
diff --git a/examples/declarative-linking/README.MD b/examples/declarative-linking/README.MD
new file mode 100644
index 0000000..8e84dcf
--- /dev/null
+++ b/examples/declarative-linking/README.MD
@@ -0,0 +1,80 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Declarative Hyperlinking Example
+================================
+
+This example demonstrates how to use the declarative hyperlink
+annotations.
+
+Contents
+--------
+
+The example consists of one resource class and one representation class
+along with a couple of model classes:
+
+`org.glassfish.jersey.examples.linking.resources.ItemResource`
+
+A resource class that produces a XML response to an HTTP GET.
+
+`com.sun.jersey.samples.linking.representation.ItemRepresentation`
+
+A JAXB representation class used to produce XML. This class also
+contains @Link and @Ref annotations to produce hyperlinks in the
+XML document.
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+---------------------- | ---------------- | --------------
+**_/items/{index}_** | ItemResource | GET
+
+Sample Response
+---------------
+
+You can access resources of this application also using curl:
+
+> curl -v http://localhost:8080/items/1
+
+In this example you should see an XML snippet with a next and a previous
+link.
+
+```xml
+<item>
+ <name>Item 1</name>
+ <link href="http://localhost:8080/items/1" rel="self"/>
+ <links>
+ <link href="http://localhost:8080/items/2" rel="next"/>
+ <link href="http://localhost:8080/items/0" rel="prev"/>
+ </links>
+</item>
+```
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys current example using Grizzly. You can access the
+application at:
+
+- <http://localhost:8080/items/0>\
+- <http://localhost:8080/items/1>\
+- <http://localhost:8080/items/2>\
+- <http://localhost:8080/items/3>\
+- <http://localhost:8080/items/4>\
+
+Notice how the first has a self link, a next link but no previous link,
+the second, third and fourth have all three and the last one only has a
+self link and prev link.
+
+Observe the HTTP headers included in the responses (e.g. via `curl -v`).
+Notice that there is a HTTP Link header for next and previous when
+appropriate.
\ No newline at end of file
diff --git a/examples/declarative-linking/pom.xml b/examples/declarative-linking/pom.xml
new file mode 100644
index 0000000..023669d
--- /dev/null
+++ b/examples/declarative-linking/pom.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>declarative-linking</artifactId>
+
+ <packaging>jar</packaging>
+ <name>jersey-examples-declarative-linking</name>
+
+ <description>Declarative Hyperlinking - Jersey Sample</description>
+
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-declarative-linking</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.el</groupId>
+ <artifactId>javax.el-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.web</groupId>
+ <artifactId>javax.el</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-moxy</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>org.eclipse.persistence.moxy</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.linking.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/App.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/App.java
new file mode 100644
index 0000000..5fac917
--- /dev/null
+++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/App.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.linking;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.examples.linking.resources.ItemsResource;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.linking.DeclarativeLinkingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Show link injection in action
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/");
+ public static final String ROOT_PATH = "items";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Declarative Linking\" Jersey Example App");
+
+ final ResourceConfig resourceConfig = new ResourceConfig(ItemsResource.class);
+ resourceConfig.register(DeclarativeLinkingFeature.class);
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.\nTry out curl -L %s%s\nStop the application using CTRL+C",
+ BASE_URI, ROOT_PATH));
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+}
diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/model/ItemModel.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/model/ItemModel.java
new file mode 100644
index 0000000..2dc21c6
--- /dev/null
+++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/model/ItemModel.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.linking.model;
+
+/**
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+public class ItemModel {
+ String name;
+
+ public ItemModel(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/model/ItemsModel.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/model/ItemsModel.java
new file mode 100644
index 0000000..50795a3
--- /dev/null
+++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/model/ItemsModel.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.linking.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Models a three entry list of items and provides a simple means of navigating
+ * the list.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+public class ItemsModel {
+
+ private List<ItemModel> items;
+ private static ItemsModel instance;
+
+ public static synchronized ItemsModel getInstance() {
+ if (instance == null) {
+ instance = new ItemsModel();
+ }
+ return instance;
+ }
+
+ private ItemsModel() {
+ items = new ArrayList<>();
+ for (int i = 0; i < 100; i++) {
+ items.add(new ItemModel("Item " + i));
+ }
+ }
+
+ public boolean hasNext(String currentId) {
+ return getIndex(currentId) < items.size() - 1;
+ }
+
+ public boolean hasPrev(String currentId) {
+ return getIndex(currentId) > 0;
+ }
+
+ public ItemModel getItem(String id) {
+ return items.get(getIndex(id));
+ }
+
+ public String getNextId(String id) {
+ return Integer.toString(getIndex(id) + 1);
+ }
+
+ public String getPrevId(String id) {
+ return Integer.toString(getIndex(id) - 1);
+ }
+
+ private int getIndex(String id) {
+ return Integer.parseInt(id);
+ }
+
+ public int getSize() {
+ return items.size();
+ }
+}
diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemRepresentation.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemRepresentation.java
new file mode 100644
index 0000000..532527a
--- /dev/null
+++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemRepresentation.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.linking.representation;
+
+import java.util.List;
+
+import javax.ws.rs.core.Link;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import org.glassfish.jersey.examples.linking.model.ItemsModel;
+import org.glassfish.jersey.examples.linking.resources.ItemResource;
+import org.glassfish.jersey.linking.Binding;
+import org.glassfish.jersey.linking.InjectLink;
+import org.glassfish.jersey.linking.InjectLink.Style;
+import org.glassfish.jersey.linking.InjectLinks;
+
+/**
+ * JAXB representation of an item
+ *
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+@XmlAccessorType(XmlAccessType.NONE)
+@XmlRootElement(name = "item")
+@InjectLinks({
+ @InjectLink(
+ resource = ItemResource.class,
+ style = Style.ABSOLUTE,
+ condition = "${instance.next}",
+ bindings = @Binding(name = "id", value = "${instance.nextId}"),
+ rel = "next"
+ ),
+ @InjectLink(
+ resource = ItemResource.class,
+ style = Style.ABSOLUTE,
+ condition = "${instance.prev}",
+ bindings = @Binding(name = "id", value = "${instance.prevId}"),
+ rel = "prev"
+ )
+})
+public class ItemRepresentation {
+
+ @XmlElement
+ private String name;
+
+ @XmlTransient
+ private String id;
+ @XmlTransient
+ private ItemsModel itemsModel;
+
+ @InjectLink(
+ resource = ItemResource.class,
+ style = Style.ABSOLUTE,
+ bindings = @Binding(name = "id", value = "${instance.id}"),
+ rel = "self"
+ )
+ @XmlJavaTypeAdapter(Link.JaxbAdapter.class)
+ @XmlElement(name = "link")
+ Link self;
+
+ @InjectLinks({
+ @InjectLink(
+ resource = ItemResource.class,
+ style = Style.ABSOLUTE,
+ condition = "${instance.next}",
+ bindings = @Binding(name = "id", value = "${instance.nextId}"),
+ rel = "next"
+ ),
+ @InjectLink(
+ resource = ItemResource.class,
+ style = Style.ABSOLUTE,
+ condition = "${instance.prev}",
+ bindings = @Binding(name = "id", value = "${instance.prevId}"),
+ rel = "prev"
+ )})
+ @XmlElement(name = "link")
+ @XmlElementWrapper(name = "links")
+ @XmlJavaTypeAdapter(Link.JaxbAdapter.class)
+ List<Link> links;
+
+ public ItemRepresentation() {
+
+ }
+
+ public ItemRepresentation(ItemsModel itemsModel, String id, String name) {
+ this.itemsModel = itemsModel;
+ this.name = name;
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public boolean isNext() {
+ return itemsModel.hasNext(id);
+ }
+
+ public boolean isPrev() {
+ return itemsModel.hasPrev(id);
+ }
+
+ public String getNextId() {
+ return itemsModel.getNextId(id);
+ }
+
+ public String getPrevId() {
+ return itemsModel.getPrevId(id);
+ }
+
+}
diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemsRepresentation.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemsRepresentation.java
new file mode 100644
index 0000000..ae2023c
--- /dev/null
+++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemsRepresentation.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.linking.representation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.core.Link;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import org.glassfish.jersey.examples.linking.model.ItemsModel;
+import org.glassfish.jersey.examples.linking.resources.ItemsResource;
+import org.glassfish.jersey.linking.Binding;
+import org.glassfish.jersey.linking.InjectLink;
+import org.glassfish.jersey.linking.InjectLink.Style;
+import org.glassfish.jersey.linking.InjectLinks;
+
+/**
+ * JAXB representation of a sublist of items
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+@XmlAccessorType(XmlAccessType.NONE)
+@XmlRootElement(name = "items")
+@InjectLinks({
+ @InjectLink(
+ resource = ItemsResource.class,
+ style = Style.ABSOLUTE,
+ method = "query",
+ condition = "${instance.offset + instance.limit < instance.modelLimit}",
+ bindings = {
+ @Binding(name = "offset", value = "${instance.offset + instance.limit}"),
+ @Binding(name = "limit", value = "${instance.limit}")
+ },
+ rel = "next"
+ ),
+ @InjectLink(
+ resource = ItemsResource.class,
+ style = Style.ABSOLUTE,
+ method = "query",
+ condition = "${instance.offset - instance.limit >= 0}",
+ bindings = {
+ @Binding(name = "offset", value = "${instance.offset - instance.limit}"),
+ @Binding(name = "limit", value = "${instance.limit}")
+ },
+ rel = "prev"
+ )})
+
+public class ItemsRepresentation {
+
+ @XmlElement(name = "items")
+ private List<ItemRepresentation> items;
+
+ @XmlTransient
+ private int offset, limit;
+
+ @XmlTransient
+ private ItemsModel itemsModel;
+
+ @InjectLink(
+ resource = ItemsResource.class,
+ method = "query",
+ style = Style.ABSOLUTE,
+ bindings = {@Binding(name = "offset", value = "${instance.offset}"),
+ @Binding(name = "limit", value = "${instance.limit}")
+ },
+ rel = "self"
+ )
+ @XmlJavaTypeAdapter(Link.JaxbAdapter.class)
+ @XmlElement(name = "link")
+ Link self;
+
+ @InjectLinks({
+ @InjectLink(
+ resource = ItemsResource.class,
+ style = Style.ABSOLUTE,
+ method = "query",
+ condition = "${instance.offset + instance.limit < instance.modelLimit}",
+ bindings = {
+ @Binding(name = "offset", value = "${instance.offset + instance.limit}"),
+ @Binding(name = "limit", value = "${instance.limit}")
+ },
+ rel = "next"
+ ),
+ @InjectLink(
+ resource = ItemsResource.class,
+ style = Style.ABSOLUTE,
+ method = "query",
+ condition = "${instance.offset - instance.limit >= 0}",
+ bindings = {
+ @Binding(name = "offset", value = "${instance.offset - instance.limit}"),
+ @Binding(name = "limit", value = "${instance.limit}")
+ },
+ rel = "prev"
+ )})
+ @XmlElement(name = "link")
+ @XmlElementWrapper(name = "links")
+ @XmlJavaTypeAdapter(Link.JaxbAdapter.class)
+ List<Link> links;
+
+ public ItemsRepresentation() {
+ offset = 0;
+ limit = 10;
+ }
+
+ public ItemsRepresentation(ItemsModel itemsModel, int offset, int limit) {
+
+ this.offset = offset;
+ this.limit = limit;
+ this.itemsModel = itemsModel;
+
+ items = new ArrayList<>();
+ for (int i = offset; i < (offset + limit) && i < itemsModel.getSize(); i++) {
+ items.add(new ItemRepresentation(
+ itemsModel,
+ Integer.toString(i),
+ itemsModel.getItem(Integer.toString(i)).getName()));
+ }
+
+ }
+
+ public int getOffset() {
+ return offset;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+
+ public int getModelLimit() {
+ return itemsModel.getSize();
+ }
+}
diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/resources/ItemResource.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/resources/ItemResource.java
new file mode 100644
index 0000000..db93267
--- /dev/null
+++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/resources/ItemResource.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.linking.resources;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.examples.linking.model.ItemModel;
+import org.glassfish.jersey.examples.linking.model.ItemsModel;
+import org.glassfish.jersey.examples.linking.representation.ItemRepresentation;
+
+/**
+ * Resource that provides access to one item from a set of items managed
+ * by ItemsModel
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+public class ItemResource {
+
+ private ItemsModel itemsModel;
+ private ItemModel itemModel;
+ private String id;
+
+ public ItemResource() {
+ throw new IllegalStateException("Only for JAX-B dressing");
+ }
+
+ public ItemResource(ItemsModel itemsModel, String id) {
+ this.id = id;
+ this.itemsModel = itemsModel;
+ try {
+ itemModel = itemsModel.getItem(id);
+ } catch (IndexOutOfBoundsException ex) {
+ throw new NotFoundException();
+ }
+ }
+
+ @GET
+ public ItemRepresentation get() {
+ return new ItemRepresentation(itemsModel, id, itemModel.getName());
+ }
+
+ public String getId() {
+ return id;
+ }
+}
diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/resources/ItemsResource.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/resources/ItemsResource.java
new file mode 100644
index 0000000..f342665
--- /dev/null
+++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/resources/ItemsResource.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.linking.resources;
+
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.examples.linking.model.ItemsModel;
+import org.glassfish.jersey.examples.linking.representation.ItemsRepresentation;
+
+/**
+ * Resource that provides access to the entire list of items
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+@Path("items")
+@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+public class ItemsResource {
+
+ private ItemsModel itemsModel;
+
+ public ItemsResource() {
+ itemsModel = ItemsModel.getInstance();
+ }
+
+ @GET
+ public ItemsRepresentation query(
+ @Context javax.ws.rs.core.UriInfo info,
+ @QueryParam("offset") @DefaultValue("-1") int offset, @DefaultValue("-1") @QueryParam("limit") int limit) {
+
+ if (offset == -1 || limit == -1) {
+ offset = offset == -1 ? 0 : offset;
+ limit = limit == -1 ? 10 : limit;
+
+ throw new WebApplicationException(
+ Response.seeOther(info.getRequestUriBuilder().queryParam("offset", offset)
+ .queryParam("limit", limit).build())
+ .build()
+ );
+ }
+
+ return new ItemsRepresentation(itemsModel, offset, limit);
+ }
+
+ @Path("{id}")
+ public ItemResource get(@PathParam("id") String id) {
+ return new ItemResource(itemsModel, id);
+ }
+
+}
diff --git a/examples/declarative-linking/src/test/java/org/glassfish/jersey/examples/linking/LinkWebAppTest.java b/examples/declarative-linking/src/test/java/org/glassfish/jersey/examples/linking/LinkWebAppTest.java
new file mode 100644
index 0000000..f5fb724
--- /dev/null
+++ b/examples/declarative-linking/src/test/java/org/glassfish/jersey/examples/linking/LinkWebAppTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.linking;
+
+import java.util.List;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.examples.linking.resources.ItemsResource;
+import org.glassfish.jersey.linking.DeclarativeLinkingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Naresh (Srinivas.Bhimisetty@Sun.Com)
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+public class LinkWebAppTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ final ResourceConfig rc = new ResourceConfig(ItemsResource.class);
+ rc.register(DeclarativeLinkingFeature.class);
+ return rc;
+ }
+
+ /**
+ * Test that the expected response is sent back.
+ */
+ @Test
+ public void testLinks() throws Exception {
+ final Response response = target().path("items")
+ .queryParam("offset", 10)
+ .queryParam("limit", "10")
+ .request(MediaType.APPLICATION_XML_TYPE)
+ .get(Response.class);
+
+ final Response.StatusType statusInfo = response.getStatusInfo();
+ assertEquals("Should have succeeded", 200, statusInfo.getStatusCode());
+
+ final String content = response.readEntity(String.class);
+ final List<Object> linkHeaders = response.getHeaders().get("Link");
+
+ assertEquals("Should have two link headers", 2, linkHeaders.size());
+ assertThat("Content should contain next link",
+ content,
+ containsString("http://localhost:" + getPort() + "/items?offset=20&limit=10"));
+ }
+}
diff --git a/examples/entity-filtering-security/README.MD b/examples/entity-filtering-security/README.MD
new file mode 100644
index 0000000..fcdbafb
--- /dev/null
+++ b/examples/entity-filtering-security/README.MD
@@ -0,0 +1,80 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Entity Data Filtering
+=====================
+
+### *Role-based Entity Filtering using security annotations*
+
+This example demonstrates how to use entity filtering feature together
+with security annotations (from `javax.annotation.security` package) and
+how to apply them on domain classes as well as on JAX-RS resource
+classes or JAX-RS resource methods.
+
+In addition to domain classes and JAX-RS resources (with security
+annotations applied) there is also one (pre-matching) container request
+filter, `SecurityRequestFilter`. The filter sets security context for
+each incoming request as if the request was invoked by a user in role
+"manager".
+
+The full description how Entity Data Filtering can be found in Jersey
+User Guide, chapter [Entity Data
+Filtering](https://jersey.java.net/documentation/latest/entity-filtering.html).
+Sections relevant to this example (describing this exact example) are:
+
+- [Enabling and configuring Entity Filtering in your
+ application](https://jersey.java.net/documentation/latest/entity-filtering.html#d0e13911)
+- [Role-based Entity Filtering using (javax.annotation.security)
+ annotations](https://jersey.java.net/documentation/latest/entity-filtering.html#ef.security.annotations)
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+
+URI path | Resource class | HTTP methods | Notes
+----------------------------------------- | ---------------------- | -------------- | ----------------------------------------------------------------------------------------------
+**_/restricted-resource/denyAll_** | RestrictedResource | GET | `@DenyAll` - returns HTTP 403, Forbidden response
+**_/restricted-resource/permitAll_** | RestrictedResource | GET | `@PermitAll` - Role-based view on RestrictedEntity class - permitAll, simpleField
+**_/restricted-resource/rolesAllowed_** | RestrictedResource | GET | `@RolesAllowed({"manager"})` - Role-based view on RestrictedEntity class - permitAll, simpleField, mixedField.managerField
+**_/unrestricted-resource_** | UnrestrictedResource | GET | No security annotation used, user in role "manager" - Role-based view on RestrictedEntity class - permitAll, simpleField, mixedField.managerField
+
+Application is based on Grizzly container (see `App`). Everything needed
+(resources/providers) is registered in
+`SecurityEntityFilteringApplication`.
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean package exec:java
+
+This deploys current example using Grizzly. You can access the
+application at:
+
+- <http://localhost:8080/unrestricted-resource>
+- <http://localhost:8080/restricted-resource/denyAll>
+- <http://localhost:8080/restricted-resource/permitAll>
+- <http://localhost:8080/restricted-resource/rolesAllowed>
+- <http://localhost:8080/restricted-resource/runtimeRolesAllowed?roles=manager,user>
+
+Using Jackson instead of MOXy
+-----------------------------
+
+This examples uses by default Entity Data Filtering feature together
+with MOXy. To switch MOXy JSON provider to Jackson (2.x) JSON provider
+simply
+
+- comment registration of MOXy ContextResolver, and\
+ `register(new MoxyJsonConfig().setFormattedOutput(true).resolver())`
+- uncomment registration of JacksonFeature\
+ `register(JacksonFeature.class)`
+
+in `SecurityEntityFilteringApplication` class.
diff --git a/examples/entity-filtering-security/pom.xml b/examples/entity-filtering-security/pom.xml
new file mode 100644
index 0000000..4eca15c
--- /dev/null
+++ b/examples/entity-filtering-security/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>entity-filtering-security</artifactId>
+ <name>jersey-examples-entity-filtering-security</name>
+
+ <description>Jersey Entity Data Filtering Security Example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-entity-filtering</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-moxy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jackson</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Run the application using "mvn exec:java" -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.entityfiltering.security.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/App.java b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/App.java
new file mode 100644
index 0000000..e8c4cdf
--- /dev/null
+++ b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/App.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.security;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Java application class starting Grizzly2 server with Entity Data Filtering with security annotations.
+ *
+ * @author Michal Gajdos
+ */
+public final class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/");
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("Jersey Entity Data Filtering Example.");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI,
+ new SecurityEntityFilteringApplication(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println("Application started.\nTry out one of these URIs:");
+ for (final String path : new String[] {"unrestricted-resource", "restricted-resource/denyAll",
+ "restricted-resource/permitAll", "restricted-resource/rolesAllowed",
+ "restricted-resource/runtimeRolesAllowed?roles=manager,user"}) {
+ System.out.println(BASE_URI + path);
+ }
+ System.out.println("Stop the application using CTRL+C");
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName())
+ .log(Level.SEVERE, "I/O error occurred during reading from an system input stream.", ex);
+ }
+ }
+}
diff --git a/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/SecurityEntityFilteringApplication.java b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/SecurityEntityFilteringApplication.java
new file mode 100644
index 0000000..d2c741b
--- /dev/null
+++ b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/SecurityEntityFilteringApplication.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.security;
+
+import javax.ws.rs.ApplicationPath;
+
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.message.filtering.SecurityEntityFilteringFeature;
+import org.glassfish.jersey.moxy.json.MoxyJsonConfig;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * Entity Data Filtering application using security annotations.
+ *
+ * @author Michal Gajdos
+ */
+@ApplicationPath("/")
+public class SecurityEntityFilteringApplication extends ResourceConfig {
+
+ public SecurityEntityFilteringApplication() {
+ // Register all resources present under the package.
+ packages("org.glassfish.jersey.examples.entityfiltering.security");
+
+ // Register entity-filtering security feature.
+ register(SecurityEntityFilteringFeature.class);
+
+ // Configure MOXy Json provider. Comment this line to use Jackson. Uncomment to use MOXy.
+ register(new MoxyJsonConfig().setFormattedOutput(true).resolver());
+
+ // Configure Jackson Json provider. Comment this line to use MOXy. Uncomment to use Jackson.
+ // register(JacksonFeature.class);
+ }
+}
diff --git a/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/domain/RestrictedEntity.java b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/domain/RestrictedEntity.java
new file mode 100644
index 0000000..324ab22
--- /dev/null
+++ b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/domain/RestrictedEntity.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.security.domain;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * Restricted entity to demonstrate various security annotations.
+ *
+ * @author Michal Gajdos
+ */
+@XmlRootElement
+public class RestrictedEntity {
+
+ private String simpleField;
+
+ private String denyAll;
+
+ private String permitAll;
+
+ private RestrictedSubEntity mixedField;
+
+ public String getSimpleField() {
+ return simpleField;
+ }
+
+ @DenyAll
+ public String getDenyAll() {
+ return denyAll;
+ }
+
+ @PermitAll
+ public String getPermitAll() {
+ return permitAll;
+ }
+
+ @RolesAllowed({"manager", "user"})
+ public RestrictedSubEntity getMixedField() {
+ return mixedField;
+ }
+
+ public void setSimpleField(final String simpleField) {
+ this.simpleField = simpleField;
+ }
+
+ public void setDenyAll(final String denyAll) {
+ this.denyAll = denyAll;
+ }
+
+ public void setPermitAll(final String permitAll) {
+ this.permitAll = permitAll;
+ }
+
+ public void setMixedField(final RestrictedSubEntity mixedField) {
+ this.mixedField = mixedField;
+ }
+
+ /**
+ * Get an instance of RestrictedEntity. This method creates always a new instance of RestrictedEntity.
+ *
+ * @return an instance of RestrictedEntity.
+ */
+ public static RestrictedEntity instance() {
+ final RestrictedEntity entity = new RestrictedEntity();
+
+ entity.setSimpleField("Simple Field.");
+ entity.setDenyAll("Deny All.");
+ entity.setPermitAll("Permit All.");
+
+ final RestrictedSubEntity mixedField = new RestrictedSubEntity();
+ mixedField.setManagerField("Manager's Field.");
+ mixedField.setUserField("User's Field.");
+
+ entity.setMixedField(mixedField);
+
+ return entity;
+ }
+}
diff --git a/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/domain/RestrictedSubEntity.java b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/domain/RestrictedSubEntity.java
new file mode 100644
index 0000000..aabc845
--- /dev/null
+++ b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/domain/RestrictedSubEntity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.security.domain;
+
+import javax.annotation.security.RolesAllowed;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * Restricted sub-entity to demonstrate that security entity-filtering is transitive.
+ *
+ * @author Michal Gajdos
+ */
+@XmlRootElement
+public class RestrictedSubEntity {
+
+ private String managerField;
+
+ private String userField;
+
+ @RolesAllowed("manager")
+ public String getManagerField() {
+ return managerField;
+ }
+
+ @RolesAllowed("user")
+ public String getUserField() {
+ return userField;
+ }
+
+ public void setManagerField(final String managerField) {
+ this.managerField = managerField;
+ }
+
+ public void setUserField(final String userField) {
+ this.userField = userField;
+ }
+}
diff --git a/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/provider/SecurityRequestFilter.java b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/provider/SecurityRequestFilter.java
new file mode 100644
index 0000000..6bf5a29
--- /dev/null
+++ b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/provider/SecurityRequestFilter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.security.provider;
+
+import java.io.IOException;
+import java.security.Principal;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.PreMatching;
+import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * @author Michal Gajdos
+ */
+@Provider
+@PreMatching
+public class SecurityRequestFilter implements ContainerRequestFilter {
+
+ @Override
+ public void filter(final ContainerRequestContext requestContext) throws IOException {
+ requestContext.setSecurityContext(new SecurityContext() {
+ @Override
+ public Principal getUserPrincipal() {
+ return new Principal() {
+ @Override
+ public String getName() {
+ return "Jersey";
+ }
+ };
+ }
+
+ @Override
+ public boolean isUserInRole(final String role) {
+ return "manager".equals(role);
+ }
+
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ return null;
+ }
+ });
+ }
+}
diff --git a/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/resource/RestrictedResource.java b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/resource/RestrictedResource.java
new file mode 100644
index 0000000..40517d3
--- /dev/null
+++ b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/resource/RestrictedResource.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.security.resource;
+
+import java.lang.annotation.Annotation;
+
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+
+import org.glassfish.jersey.examples.entityfiltering.security.domain.RestrictedEntity;
+import org.glassfish.jersey.internal.util.Tokenizer;
+import org.glassfish.jersey.message.filtering.SecurityAnnotations;
+
+/**
+ * Resource restricted with security annotations. Security restrictions are defined by resource methods and
+ * {@link RestrictedEntity}.
+ *
+ * @author Michal Gajdos
+ */
+@Path("restricted-resource")
+@Produces("application/json")
+public class RestrictedResource {
+
+ @GET
+ @Path("denyAll")
+ @DenyAll
+ public RestrictedEntity denyAll() {
+ return RestrictedEntity.instance();
+ }
+
+ @GET
+ @Path("permitAll")
+ @PermitAll
+ public RestrictedEntity permitAll() {
+ return RestrictedEntity.instance();
+ }
+
+ @GET
+ @Path("rolesAllowed")
+ @RolesAllowed({"manager"})
+ public RestrictedEntity rolesAllowed() {
+ return RestrictedEntity.instance();
+ }
+
+ @GET
+ @Path("runtimeRolesAllowed")
+ public Response runtimeRolesAllowed(@QueryParam("roles") @DefaultValue("") final String roles) {
+ return Response
+ .ok()
+ .entity(RestrictedEntity.instance(),
+ new Annotation[] {SecurityAnnotations.rolesAllowed(Tokenizer.tokenize(roles))})
+ .build();
+ }
+}
diff --git a/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/resource/UnrestrictedResource.java b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/resource/UnrestrictedResource.java
new file mode 100644
index 0000000..76e2094
--- /dev/null
+++ b/examples/entity-filtering-security/src/main/java/org/glassfish/jersey/examples/entityfiltering/security/resource/UnrestrictedResource.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.security.resource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import org.glassfish.jersey.examples.entityfiltering.security.domain.RestrictedEntity;
+
+/**
+ * Resource not restricted with security annotations leaving security restrictions solely to {@link RestrictedEntity} and
+ * {@link javax.ws.rs.core.SecurityContext}.
+ *
+ * @author Michal Gajdos
+ */
+@Path("unrestricted-resource")
+@Produces("application/json")
+public class UnrestrictedResource {
+
+ @GET
+ public RestrictedEntity getRestrictedEntity() {
+ return RestrictedEntity.instance();
+ }
+}
diff --git a/examples/entity-filtering-security/src/test/java/org/glassfish/jersey/examples/entityfiltering/security/RestrictedResourceTest.java b/examples/entity-filtering-security/src/test/java/org/glassfish/jersey/examples/entityfiltering/security/RestrictedResourceTest.java
new file mode 100644
index 0000000..1a530b7
--- /dev/null
+++ b/examples/entity-filtering-security/src/test/java/org/glassfish/jersey/examples/entityfiltering/security/RestrictedResourceTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.security;
+
+import java.util.Arrays;
+
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.examples.entityfiltering.security.domain.RestrictedEntity;
+import org.glassfish.jersey.examples.entityfiltering.security.domain.RestrictedSubEntity;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.message.filtering.SecurityEntityFilteringFeature;
+import org.glassfish.jersey.moxy.json.MoxyJsonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * {@link org.glassfish.jersey.examples.entityfiltering.security.resource.RestrictedResource} unit tests.
+ *
+ * @author Michal Gajdos
+ */
+@RunWith(Parameterized.class)
+public class RestrictedResourceTest extends JerseyTest {
+
+ @Parameterized.Parameters(name = "Provider: {0}")
+ public static Iterable<Class[]> providers() {
+ return Arrays.asList(new Class[][]{{MoxyJsonFeature.class}, {JacksonFeature.class}});
+ }
+
+ public RestrictedResourceTest(final Class<Feature> filteringProvider) {
+ super(new ResourceConfig(SecurityEntityFilteringFeature.class)
+ .packages("org.glassfish.jersey.examples.entityfiltering.security")
+ .register(filteringProvider));
+
+ enable(TestProperties.DUMP_ENTITY);
+ enable(TestProperties.LOG_TRAFFIC);
+ }
+
+ @Test
+ public void testDenyAll() throws Exception {
+ assertThat(target("restricted-resource").path("denyAll").request().get().getStatus(),
+ equalTo(Response.Status.FORBIDDEN.getStatusCode()));
+ }
+
+ @Test
+ public void testPermitAll() throws Exception {
+ final RestrictedEntity entity = target("restricted-resource").path("permitAll").request().get(RestrictedEntity.class);
+ final RestrictedSubEntity mixedField = entity.getMixedField();
+
+ // Not null values.
+ assertThat(entity.getSimpleField(), notNullValue());
+ assertThat(entity.getPermitAll(), notNullValue());
+
+ // Null values.
+ assertThat(entity.getDenyAll(), nullValue());
+ assertThat(mixedField, nullValue());
+ }
+
+ @Test
+ public void testRolesAllowed() throws Exception {
+ final RestrictedEntity entity = target("restricted-resource").path("rolesAllowed").request().get(RestrictedEntity.class);
+ final RestrictedSubEntity mixedField = entity.getMixedField();
+
+ // Not null values.
+ assertThat(entity.getSimpleField(), notNullValue());
+ assertThat(entity.getPermitAll(), notNullValue());
+ assertThat(mixedField, notNullValue());
+ assertThat(mixedField.getManagerField(), notNullValue());
+
+ // Null values.
+ assertThat(entity.getDenyAll(), nullValue());
+ assertThat(mixedField.getUserField(), nullValue());
+ }
+
+ @Test
+ public void testRuntimeRolesAllowedUser() throws Exception {
+ final RestrictedEntity entity = target("restricted-resource")
+ .path("runtimeRolesAllowed")
+ .queryParam("roles", "user")
+ .request().get(RestrictedEntity.class);
+ final RestrictedSubEntity mixedField = entity.getMixedField();
+
+ // Not null values.
+ assertThat(entity.getSimpleField(), notNullValue());
+ assertThat(entity.getPermitAll(), notNullValue());
+ assertThat(mixedField, notNullValue());
+ assertThat(mixedField.getUserField(), notNullValue());
+
+ // Null values.
+ assertThat(entity.getDenyAll(), nullValue());
+ assertThat(mixedField.getManagerField(), nullValue());
+ }
+
+ @Test
+ public void testRuntimeRolesAllowedManagerUser() throws Exception {
+ final RestrictedEntity entity = target("restricted-resource")
+ .path("runtimeRolesAllowed")
+ .queryParam("roles", "user,manager")
+ .request().get(RestrictedEntity.class);
+ final RestrictedSubEntity mixedField = entity.getMixedField();
+
+ // Not null values.
+ assertThat(entity.getSimpleField(), notNullValue());
+ assertThat(entity.getPermitAll(), notNullValue());
+ assertThat(mixedField, notNullValue());
+ assertThat(mixedField.getUserField(), notNullValue());
+ assertThat(mixedField.getManagerField(), notNullValue());
+
+ // Null values.
+ assertThat(entity.getDenyAll(), nullValue());
+ }
+
+ @Test
+ public void testRuntimeRolesAllowedInvalid() throws Exception {
+ final RestrictedEntity entity = target("restricted-resource")
+ .path("runtimeRolesAllowed")
+ .queryParam("roles", "invalid")
+ .request().get(RestrictedEntity.class);
+ final RestrictedSubEntity mixedField = entity.getMixedField();
+
+ // Not null values.
+ assertThat(entity.getSimpleField(), notNullValue());
+ assertThat(entity.getPermitAll(), notNullValue());
+
+ // Null values.
+ assertThat(entity.getDenyAll(), nullValue());
+ assertThat(mixedField, nullValue());
+ }
+}
diff --git a/examples/entity-filtering-security/src/test/java/org/glassfish/jersey/examples/entityfiltering/security/UnrestrictedResourceTest.java b/examples/entity-filtering-security/src/test/java/org/glassfish/jersey/examples/entityfiltering/security/UnrestrictedResourceTest.java
new file mode 100644
index 0000000..5b6a838
--- /dev/null
+++ b/examples/entity-filtering-security/src/test/java/org/glassfish/jersey/examples/entityfiltering/security/UnrestrictedResourceTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.security;
+
+import java.util.Arrays;
+
+import javax.ws.rs.core.Feature;
+
+import org.glassfish.jersey.examples.entityfiltering.security.domain.RestrictedEntity;
+import org.glassfish.jersey.examples.entityfiltering.security.domain.RestrictedSubEntity;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.message.filtering.SecurityEntityFilteringFeature;
+import org.glassfish.jersey.moxy.json.MoxyJsonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * {@link org.glassfish.jersey.examples.entityfiltering.security.resource.UnrestrictedResource} unit tests.
+ *
+ * @author Michal Gajdos
+ */
+@RunWith(Parameterized.class)
+public class UnrestrictedResourceTest extends JerseyTest {
+
+ @Parameterized.Parameters(name = "Provider: {0}")
+ public static Iterable<Class[]> providers() {
+ return Arrays.asList(new Class[][]{{MoxyJsonFeature.class}, {JacksonFeature.class}});
+ }
+
+ public UnrestrictedResourceTest(final Class<Feature> filteringProvider) {
+ super(new ResourceConfig(SecurityEntityFilteringFeature.class)
+ .packages("org.glassfish.jersey.examples.entityfiltering.security")
+ .register(filteringProvider));
+
+ enable(TestProperties.DUMP_ENTITY);
+ enable(TestProperties.LOG_TRAFFIC);
+ }
+
+ @Test
+ public void testRestrictedEntity() throws Exception {
+ final RestrictedEntity entity = target("unrestricted-resource").request().get(RestrictedEntity.class);
+ final RestrictedSubEntity mixedField = entity.getMixedField();
+
+ // Not null values.
+ assertThat(entity.getSimpleField(), notNullValue());
+ assertThat(entity.getPermitAll(), notNullValue());
+ assertThat(mixedField, notNullValue());
+ assertThat(mixedField.getManagerField(), notNullValue());
+
+ // Null values.
+ assertThat(entity.getDenyAll(), nullValue());
+ assertThat(mixedField.getUserField(), nullValue());
+ }
+}
diff --git a/examples/entity-filtering-selectable/README.MD b/examples/entity-filtering-selectable/README.MD
new file mode 100644
index 0000000..7241635
--- /dev/null
+++ b/examples/entity-filtering-selectable/README.MD
@@ -0,0 +1,99 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Entity Data Filtering Dynamic and Configurable Query Parameters
+===============================================================
+
+This example demonstrates how to use entity filtering feature
+dynamically with defining what should be sent to the other side based on
+query parameters and how to apply the rules on domain classes as well as
+on JAX-RS resource classes or JAX-RS resource methods.
+
+The full description how Entity Data Filtering can be found in Jersey
+User Guide, chapter [Entity Data
+Filtering](https://jersey.java.net/documentation/latest/entity-filtering.html).
+Sections relevant to this example (describing this exact example) are:
+
+- [Enabling and configuring Entity Filtering in your
+ application](https://jersey.java.net/documentation/latest/entity-filtering.html#d0e13911)
+- [Entity Filtering based on dynamic and configurable query
+ parameters](https://jersey.java.net/documentation/latest/entity-filtering.html#ef.selectable.annotations)
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Allowed values | Notes
+------------------------------------------ | ---------------- | -------------- | ---------------------------------------------------------------------------- | ----------------------------------------------
+**_/people/{id}?select={select}_** | PersonResource | GET | id = any value, select = field names from Person/Address/PhoneNumber class | Returns fields of Person/Address/PhoneNumber
+**_/people/{id}/addresses?select={select}_** | PersonResource | GET | id = any value, select = field names from Address/PhoneNumber class | Returns fields of Address/PhoneNumber
+
+Application is based on Grizzly container (see `App`). Everything needed
+(resources/providers) is registered in
+`SelectableEntityFilteringApplication`.
+
+Sample Response
+---------------
+
+Even though the same instance of, e.g. Person class, is used to create
+response the given values of `select` query parameter are used to select
+the fields that would be transferred over the wire. For
+`people/1234?select=familyName,givenName` it looks like:
+
+```javascript
+{
+ "familyName": "Dowd",
+ "givenName": "Andrew"
+}
+```
+
+And for `people/1234?select=familyName,givenName,addresses.phoneNumber.number` it looks like:
+
+```javascript
+{
+ "addresses":[
+ {
+ "phoneNumber":{
+ "number": "867-5309"
+ }
+ }
+ ],
+ "familyName": "Dowd",
+ "givenName": "Andrew"
+}
+```
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean package exec:java
+
+This deploys current example using Grizzly. You can access the
+application at:
+
+- <http://localhost:8080/people/1234>
+- <http://localhost:8080/people/1234?select=familyName,givenName>
+- <http://localhost:8080/people/1234?select=region,addresses.region>
+- <http://localhost:8080/people/1234?select=familyName,givenName,addresses.phoneNumber.number>
+
+Using Jackson instead of MOXy
+-----------------------------
+
+This examples uses by default Entity Data Filtering feature together
+with MOXy. To switch MOXy JSON provider to Jackson (2.x) JSON provider
+simply
+
+- comment registration of MOXy ContextResolver, and\
+ `register(new MoxyJsonConfig().setFormattedOutput(true).resolver())`
+- uncomment registration of JacksonFeature\
+ `register(JacksonFeature.class)`
+
+in `SelectableEntityFilteringApplication` class.
diff --git a/examples/entity-filtering-selectable/pom.xml b/examples/entity-filtering-selectable/pom.xml
new file mode 100644
index 0000000..fa1acc5
--- /dev/null
+++ b/examples/entity-filtering-selectable/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>entity-filtering-selectable</artifactId>
+ <name>jersey-examples-entity-filtering-selectable</name>
+
+ <description>Jersey Entity Data Filtering Selectable Example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-entity-filtering</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-moxy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jackson</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Run the application using "mvn exec:java" -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.entityfiltering.selectable.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/App.java b/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/App.java
new file mode 100644
index 0000000..67c0082
--- /dev/null
+++ b/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/App.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.selectable;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+
+/**
+ * Java application class starting Grizzly2 server with Entity Data Filtering with query parameters.
+ *
+ * @author Andy Pemberton (pembertona at oracle.com)
+ */
+public final class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/");
+
+ public static void main(final String[] args) {
+ try {
+ System.out.println("Jersey Entity Data Filtering Example.");
+
+ final HttpServer server = GrizzlyHttpServerFactory
+ .createHttpServer(BASE_URI, new SelectableEntityFilteringApplication(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println("Application started.\nTry out one of these URIs:");
+ for (final String path : new String[]{"people/1234", "people/1234?select=familyName,givenName",
+ "people/1234?select=region,addresses.region",
+ "people/1234?select=familyName,givenName,addresses.phoneNumber.number"}) {
+ System.out.println(BASE_URI + path);
+ }
+ System.out.println("Stop the application using CTRL+C");
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Prevent instantiation.
+ */
+ private App() {
+ }
+}
diff --git a/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/SelectableEntityFilteringApplication.java b/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/SelectableEntityFilteringApplication.java
new file mode 100644
index 0000000..4e94e3a
--- /dev/null
+++ b/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/SelectableEntityFilteringApplication.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.selectable;
+
+import javax.ws.rs.ApplicationPath;
+
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.message.filtering.SelectableEntityFilteringFeature;
+import org.glassfish.jersey.moxy.json.MoxyJsonConfig;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * Entity Data Filtering application using request parameters.
+ *
+ * @author Andy Pemberton (pembertona at gmail.com)
+ * @author Michal Gajdos
+ */
+@ApplicationPath("/")
+public class SelectableEntityFilteringApplication extends ResourceConfig {
+
+ public SelectableEntityFilteringApplication() {
+ // Register all resources present under the package.
+ packages("org.glassfish.jersey.examples.entityfiltering.selectable");
+
+ // Register entity-filtering selectable feature.
+ register(SelectableEntityFilteringFeature.class);
+ property(SelectableEntityFilteringFeature.QUERY_PARAM_NAME, "select");
+
+ // Configure MOXy Json provider. Comment this line to use Jackson. Uncomment to use MOXy.
+ register(new MoxyJsonConfig().setFormattedOutput(true).resolver());
+
+ // Configure Jackson Json provider. Comment this line to use MOXy. Uncomment to use Jackson.
+ // register(JacksonFeature.class);
+ }
+}
diff --git a/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/domain/Address.java b/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/domain/Address.java
new file mode 100644
index 0000000..cc4edbb
--- /dev/null
+++ b/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/domain/Address.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.selectable.domain;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class Address {
+
+ private String streetAddress;
+
+ private String region;
+
+ private PhoneNumber phoneNumber;
+
+ public String getStreetAddress() {
+ return streetAddress;
+ }
+
+ public void setStreetAddress(String streetAddress) {
+ this.streetAddress = streetAddress;
+ }
+
+ public String getRegion() {
+ return region;
+ }
+
+ public void setRegion(String region) {
+ this.region = region;
+ }
+
+ public PhoneNumber getPhoneNumber() {
+ return phoneNumber;
+ }
+
+ public void setPhoneNumber(PhoneNumber phoneNumber) {
+ this.phoneNumber = phoneNumber;
+ }
+
+}
diff --git a/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/domain/Person.java b/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/domain/Person.java
new file mode 100644
index 0000000..8d4b9b5
--- /dev/null
+++ b/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/domain/Person.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.selectable.domain;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class Person {
+ private String givenName;
+
+ private String familyName;
+
+ private String honorificSuffix;
+
+ private String honorificPrefix;
+
+ // same name as Address.region
+ private String region;
+
+ private List<Address> addresses;
+
+ private Map<String, PhoneNumber> phoneNumbers;
+
+ public String getGivenName() {
+ return givenName;
+ }
+
+ public void setGivenName(String givenName) {
+ this.givenName = givenName;
+ }
+
+ public String getFamilyName() {
+ return familyName;
+ }
+
+ public void setFamilyName(String familyName) {
+ this.familyName = familyName;
+ }
+
+ public String getHonorificSuffix() {
+ return honorificSuffix;
+ }
+
+ public void setHonorificSuffix(String honorificSuffix) {
+ this.honorificSuffix = honorificSuffix;
+ }
+
+ public String getHonorificPrefix() {
+ return honorificPrefix;
+ }
+
+ public void setHonorificPrefix(String honorificPrefix) {
+ this.honorificPrefix = honorificPrefix;
+ }
+
+ public List<Address> getAddresses() {
+ return addresses;
+ }
+
+ public void setAddresses(List<Address> addresses) {
+ this.addresses = addresses;
+ }
+
+ public String getRegion() {
+ return region;
+ }
+
+ public void setRegion(String region) {
+ this.region = region;
+ }
+
+ public Map<String, PhoneNumber> getPhoneNumbers() {
+ return phoneNumbers;
+ }
+
+ public void setPhoneNumbers(Map<String, PhoneNumber> phoneNumbers) {
+ this.phoneNumbers = phoneNumbers;
+ }
+}
diff --git a/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/domain/PhoneNumber.java b/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/domain/PhoneNumber.java
new file mode 100644
index 0000000..de8e89e
--- /dev/null
+++ b/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/domain/PhoneNumber.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.selectable.domain;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class PhoneNumber {
+
+ private String areaCode;
+
+ private String number;
+
+ public String getNumber() {
+ return number;
+ }
+
+ public void setNumber(String number) {
+ this.number = number;
+ }
+
+ public String getAreaCode() {
+ return areaCode;
+ }
+
+ public void setAreaCode(String areaCode) {
+ this.areaCode = areaCode;
+ }
+
+}
diff --git a/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/resource/PersonResource.java b/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/resource/PersonResource.java
new file mode 100644
index 0000000..45bb8b6
--- /dev/null
+++ b/examples/entity-filtering-selectable/src/main/java/org/glassfish/jersey/examples/entityfiltering/selectable/resource/PersonResource.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.selectable.resource;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import org.glassfish.jersey.examples.entityfiltering.selectable.domain.Address;
+import org.glassfish.jersey.examples.entityfiltering.selectable.domain.Person;
+import org.glassfish.jersey.examples.entityfiltering.selectable.domain.PhoneNumber;
+
+/**
+ * Resource to support query parameter driven entity filtering.
+ *
+ * @author Andy Pemberton (pembertona at gmail.com)
+ */
+@Path("people")
+@Produces("application/json")
+public class PersonResource {
+
+ @GET
+ @Path("{id}")
+ public Person getPerson() {
+ final Person person = new Person();
+ person.setGivenName("Andrew");
+ person.setFamilyName("Dowd");
+ person.setHonorificPrefix("Mr.");
+ person.setHonorificSuffix("PhD");
+ person.setRegion("1st Level Region");
+
+ final ArrayList<Address> addresses = new ArrayList<>();
+ person.setAddresses(addresses);
+
+ final Address address = new Address();
+ addresses.add(address);
+ address.setRegion("2nd Level Region");
+ address.setStreetAddress("1234 fake st.");
+ address.setPhoneNumber(new PhoneNumber());
+ address.getPhoneNumber().setNumber("867-5309");
+ address.getPhoneNumber().setAreaCode("540");
+
+ person.setPhoneNumbers(new HashMap<String, PhoneNumber>());
+ final PhoneNumber number = new PhoneNumber();
+ number.setAreaCode("804");
+ number.setNumber("867-5309");
+ person.getPhoneNumbers().put("HOME", number);
+
+ return person;
+ }
+
+ @GET
+ @Path("{id}/addresses")
+ public Address getAddress() {
+ return this.getPerson().getAddresses().get(0);
+ }
+}
diff --git a/examples/entity-filtering-selectable/src/test/java/org/glassfish/jersey/examples/entityfiltering/selectable/PersonResourceTest.java b/examples/entity-filtering-selectable/src/test/java/org/glassfish/jersey/examples/entityfiltering/selectable/PersonResourceTest.java
new file mode 100644
index 0000000..dc0a275
--- /dev/null
+++ b/examples/entity-filtering-selectable/src/test/java/org/glassfish/jersey/examples/entityfiltering/selectable/PersonResourceTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.selectable;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Feature;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.examples.entityfiltering.selectable.domain.Address;
+import org.glassfish.jersey.examples.entityfiltering.selectable.domain.Person;
+import org.glassfish.jersey.examples.entityfiltering.selectable.domain.PhoneNumber;
+import org.glassfish.jersey.examples.entityfiltering.selectable.resource.PersonResource;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.message.filtering.SelectableEntityFilteringFeature;
+import org.glassfish.jersey.moxy.json.MoxyJsonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * {@link PersonResource} unit tests.
+ *
+ * @author Andy Pemberton (pembertona at gmail.com)
+ * @author Michal Gajdos
+ */
+@RunWith(Parameterized.class)
+public class PersonResourceTest extends JerseyTest {
+
+ @Parameterized.Parameters(name = "Provider: {0}")
+ public static Iterable<Class[]> providers() {
+ return Arrays.asList(new Class[][]{{MoxyJsonFeature.class}, {JacksonFeature.class}});
+ }
+
+ private final Class<Feature> filteringProvider;
+
+ public PersonResourceTest(final Class<Feature> filteringProvider) {
+ super(new ResourceConfig(SelectableEntityFilteringFeature.class)
+ .packages("org.glassfish.jersey.examples.entityfiltering.selectable")
+ .property(SelectableEntityFilteringFeature.QUERY_PARAM_NAME, "select")
+ .register(filteringProvider));
+
+ this.filteringProvider = filteringProvider;
+
+ enable(TestProperties.DUMP_ENTITY);
+ enable(TestProperties.LOG_TRAFFIC);
+ }
+
+ @Override
+ protected void configureClient(final ClientConfig config) {
+ config.register(filteringProvider);
+ }
+
+ @Test
+ public void testNoFilter() throws Exception {
+ final Person entity = target("people").path("1234").request().get(Person.class);
+
+ // Not null values.
+ assertThat(entity.getFamilyName(), notNullValue());
+ assertThat(entity.getGivenName(), notNullValue());
+ assertThat(entity.getHonorificPrefix(), notNullValue());
+ assertThat(entity.getHonorificSuffix(), notNullValue());
+ assertThat(entity.getRegion(), notNullValue());
+
+ final List<Address> addresses = entity.getAddresses();
+ assertThat(addresses, notNullValue());
+ final Address address = addresses.get(0);
+ assertThat(address, notNullValue());
+ assertThat(address.getRegion(), notNullValue());
+ assertThat(address.getStreetAddress(), notNullValue());
+ PhoneNumber phoneNumber = address.getPhoneNumber();
+ assertThat(phoneNumber, notNullValue());
+ assertThat(phoneNumber.getAreaCode(), notNullValue());
+ assertThat(phoneNumber.getNumber(), notNullValue());
+
+ final Map<String, PhoneNumber> phoneNumbers = entity.getPhoneNumbers();
+ assertThat(phoneNumbers, notNullValue());
+
+ // TODO: enable for MOXy as well when JERSEY-2751 gets fixed.
+ if (JacksonFeature.class.isAssignableFrom(filteringProvider)) {
+ phoneNumber = phoneNumbers.get("HOME");
+ assertThat(phoneNumber, notNullValue());
+ assertThat(phoneNumber.getAreaCode(), notNullValue());
+ assertThat(phoneNumber.getNumber(), notNullValue());
+ }
+ }
+
+ @Test
+ public void testInvalidFilter() throws Exception {
+ final Person entity = target("people").path("1234")
+ .queryParam("select", "invalid").request().get(Person.class);
+
+ // All null values.
+ assertThat(entity.getFamilyName(), nullValue());
+ assertThat(entity.getGivenName(), nullValue());
+ assertThat(entity.getHonorificPrefix(), nullValue());
+ assertThat(entity.getHonorificSuffix(), nullValue());
+ assertThat(entity.getRegion(), nullValue());
+ assertThat(entity.getAddresses(), nullValue());
+ assertThat(entity.getPhoneNumbers(), nullValue());
+ }
+
+ /**
+ * Test first level filters.
+ */
+ @Test
+ public void testFilters() throws Exception {
+ final Person entity = target("people").path("1234")
+ .queryParam("select", "familyName,givenName").request()
+ .get(Person.class);
+
+ // Not null values.
+ assertThat(entity.getFamilyName(), notNullValue());
+ assertThat(entity.getGivenName(), notNullValue());
+
+ // Null values.
+ assertThat(entity.getAddresses(), nullValue());
+ assertThat(entity.getPhoneNumbers(), nullValue());
+ assertThat(entity.getRegion(), nullValue());
+ }
+
+ /**
+ * Test 2nd and 3rd level filters.
+ */
+ @Test
+ public void testSubFilters() throws Exception {
+ final Person entity = target("people")
+ .path("1234")
+ .queryParam("select",
+ "familyName,givenName,addresses.streetAddress,addresses.phoneNumber.areaCode")
+ .request().get(Person.class);
+
+ // Not null values.
+ assertThat(entity.getFamilyName(), notNullValue());
+ assertThat(entity.getGivenName(), notNullValue());
+ assertThat(entity.getAddresses().get(0).getStreetAddress(), notNullValue());
+ assertThat(entity.getAddresses().get(0).getPhoneNumber().getAreaCode(), notNullValue());
+
+ // Null values.
+ assertThat(entity.getRegion(), nullValue());
+ assertThat(entity.getAddresses().get(0).getPhoneNumber().getNumber(), nullValue());
+ }
+
+ /**
+ * Test that 1st and 2nd level filters with the same name act as expected.
+ */
+ @Test
+ public void testFiltersSameName() throws Exception {
+ final Person firstLevel = target("people").path("1234")
+ .queryParam("select", "familyName,region").request()
+ .get(Person.class);
+ final Person secondLevel = target("people").path("1234")
+ .queryParam("select", "familyName,addresses.region").request()
+ .get(Person.class);
+
+ // Not null values.
+ assertThat(firstLevel.getRegion(), notNullValue());
+ assertThat(secondLevel.getAddresses().get(0).getRegion(), notNullValue());
+
+ // Null values.
+ assertThat(firstLevel.getAddresses(), nullValue()); //confirms 2nd level region on addresses is null
+ assertThat(secondLevel.getRegion(), nullValue());
+ }
+
+}
diff --git a/examples/entity-filtering/README.MD b/examples/entity-filtering/README.MD
new file mode 100644
index 0000000..96c2539
--- /dev/null
+++ b/examples/entity-filtering/README.MD
@@ -0,0 +1,114 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Entity Data Filtering
+=====================
+
+### *Entity Data Filtering Using custom annotations to filter entities*
+
+This example demonstrates how to define custom entity filtering
+annotations (views) and how to apply them on domain classes as well as
+on JAX-RS resource classes or JAX-RS resource methods.
+
+The full description how Entity Data Filtering can be found in Jersey User Guide, chapter
+[Entity Data Filtering](https://jersey.java.net/documentation/latest/entity-filtering.html).
+Sections relevant to this example (describing this exact example) are:
+
+- [Enabling and configuring Entity Filtering in your application](https://jersey.java.net/documentation/latest/entity-filtering.html#d0e13911)
+- [Components used to describe Entity Filtering concepts](https://jersey.java.net/documentation/latest/entity-filtering.html#d0e14024)
+- [Using custom annotations to filter entities](https://jersey.java.net/documentation/latest/entity-filtering.html#ef.annotations)
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Allowed values
+----------------------------------------- | ------------------ | -------------- | ----------------
+**_/projects/{id}_** | ProjectsResource | GET | int
+**_/projects_** | ProjectsResource | GET | N/A
+**_/projects/detailed/{id}_** | ProjectsResource | GET | int
+**_/projects/detailed_** | ProjectsResource | GET | N/A
+**_/tasks/{id}?detailed={true\|false}_** | TasksResource | GET | int, boolean
+**_/tasks_** | TasksResource | GET | N/A
+**_/tasks/detailed_** | TasksResource | GET | N/A
+**_/users/{id}?detailed={true\|false}_** | UsersResource | GET | int, boolean
+**_/tasks?detailed={true\|false}_** | UsersResource | GET | N/A
+
+Application is based on Grizzly container (see `App`). Everything needed
+(resources/providers) is registered in `EntityFilteringApplication`.
+
+Sample Response
+---------------
+
+Even though the same instance of, e.g. Project class, is used to create
+response for both basic and detailed view the actual data sent over the
+wire differ for each of these two views. For basic view it looks like:
+
+```javascript
+{
+ "description" : "Jersey is the open source (under dual EPL+GPL license) JAX-RS 2.1 (JSR 370) production quality Reference Implementation for building RESTful Web services.",
+ "id" : 1,
+ "name" : "Jersey"
+}
+```
+
+And for detailed view it looks like:
+
+```javascript
+{
+ "description" : "Jersey is the open source (under dual EPL+GPL license) JAX-RS 2.1 (JSR 370) production quality Reference Implementation for building RESTful Web services.",
+ "id" : 1,
+ "name" : "Jersey",
+ "tasks" : [ {
+ "description" : "Entity Data Filtering",
+ "id" : 1,
+ "name" : "ENT_FLT"
+ }, {
+ "description" : "OAuth 1 + 2",
+ "id" : 2,
+ "name" : "OAUTH"
+ } ],
+ "users" : [ {
+ "email" : "very@secret.com",
+ "id" : 1,
+ "name" : "Jersey Robot"
+ } ]
+}
+```
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean package exec:java
+
+This deploys current example using Grizzly. You can access the
+application at:
+
+- <http://localhost:8080/projects>
+- <http://localhost:8080/projects/detailed>
+- <http://localhost:8080/users>
+- <http://localhost:8080/users?detailed=true>
+- <http://localhost:8080/tasks>
+- <http://localhost:8080/tasks/detailed>
+
+Using Jackson instead of MOXy
+-----------------------------
+
+This examples uses by default Entity Data Filtering feature together
+with MOXy. To switch MOXy JSON provider to Jackson (2.x) JSON provider
+simply
+
+- comment registration of MOXy ContextResolver, and\
+ `register(new MoxyJsonConfig().setFormattedOutput(true).resolver())`
+- uncomment registration of JacksonFeature\
+ `register(JacksonFeature.class)`
+
+in `EntityFilteringApplication` class.
diff --git a/examples/entity-filtering/pom.xml b/examples/entity-filtering/pom.xml
new file mode 100644
index 0000000..0d4f615
--- /dev/null
+++ b/examples/entity-filtering/pom.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>entity-filtering</artifactId>
+ <name>jersey-examples-entity-filtering</name>
+
+ <description>Jersey Entity Data Filtering Example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-entity-filtering</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-moxy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jackson</artifactId>
+ </dependency>
+
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Run the application using "mvn exec:java" -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.entityfiltering.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/App.java b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/App.java
new file mode 100644
index 0000000..bcdb95f
--- /dev/null
+++ b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/App.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Java application class starting Grizzly2 server with Entity Data Filtering app deployed.
+ *
+ * @author Michal Gajdos
+ */
+public final class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/");
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("Jersey Entity Data Filtering Example.");
+
+ final HttpServer server = GrizzlyHttpServerFactory
+ .createHttpServer(BASE_URI, new EntityFilteringApplication(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println("Application started.\nTry out one of these URIs:");
+ for (final String path : new String[]{"projects", "projects/detailed", "users", "users?detailed=true", "tasks",
+ "tasks/detailed"}) {
+ System.out.println(BASE_URI + path);
+ }
+ System.out.println("Stop the application using CTRL+C");
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName())
+ .log(Level.SEVERE, "I/O error occurred during reading from an system input stream.", ex);
+ }
+ }
+}
diff --git a/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/EntityFilteringApplication.java b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/EntityFilteringApplication.java
new file mode 100644
index 0000000..a98c5d4
--- /dev/null
+++ b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/EntityFilteringApplication.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering;
+
+import javax.ws.rs.ApplicationPath;
+
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.message.filtering.EntityFilteringFeature;
+import org.glassfish.jersey.moxy.json.MoxyJsonConfig;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * Entity Data Filtering application.
+ *
+ * @author Michal Gajdos
+ */
+@ApplicationPath("/")
+public class EntityFilteringApplication extends ResourceConfig {
+
+ public EntityFilteringApplication() {
+ // Register all resources present under the package.
+ packages("org.glassfish.jersey.examples.entityfiltering");
+
+ // Entity Data Filtering feature.
+ register(EntityFilteringFeature.class);
+
+ // Configure MOXy Json provider. Comment this line to use Jackson. Uncomment to use MOXy.
+ register(new MoxyJsonConfig().setFormattedOutput(true).resolver());
+
+ // Configure Jackson Json provider. Comment this line to use MOXy. Uncomment to use Jackson.
+ // register(JacksonFeature.class);
+ }
+}
diff --git a/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/domain/EntityStore.java b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/domain/EntityStore.java
new file mode 100644
index 0000000..0d99eda
--- /dev/null
+++ b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/domain/EntityStore.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.domain;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Entity-store utility class. Class creates a sample instance of each entity.
+ *
+ * @author Michal Gajdos
+ */
+@SuppressWarnings({"JavaDoc", "UnusedDeclaration"})
+public final class EntityStore {
+
+ private static final Map<Long, Project> PROJECTS = new LinkedHashMap<>();
+ private static final Map<Long, User> USERS = new LinkedHashMap<>();
+ private static final Map<Long, Task> TASKS = new LinkedHashMap<>();
+
+ static {
+ // Projects.
+ final Project project = createProject("Jersey", "Jersey is the open source (under dual EPL+GPL license) JAX-RS 2.1 "
+ + "(JSR 370) production quality Reference Implementation for building RESTful Web services.");
+
+ // Users.
+ final User robot = createUser("Jersey Robot", "very@secret.com");
+
+ // Tasks.
+ final Task filtering = createTask("ENT_FLT", "Entity Data Filtering");
+ final Task oauth = createTask("OAUTH", "OAuth 1 + 2");
+
+ // Project -> Users, Tasks.
+ add(project, robot);
+ filtering.setProject(project);
+ oauth.setProject(project);
+
+ // Users -> Projects, Tasks.
+ add(robot, project);
+ filtering.setUser(robot);
+ oauth.setUser(robot);
+
+ // Tasks -> Projects, Users.
+ add(filtering, project);
+ add(oauth, project);
+ add(filtering, robot);
+ add(oauth, robot);
+ }
+
+ public static void add(final Project project, final User user) {
+ user.getProjects().add(project);
+ }
+
+ public static void add(final User user, final Project project) {
+ project.getUsers().add(user);
+ }
+
+ public static void add(final Task task, final User user) {
+ user.getTasks().add(task);
+ }
+
+ public static void add(final Task task, final Project project) {
+ project.getTasks().add(task);
+ }
+
+ public static Project createProject(final String name, final String description) {
+ return createProject(name, description, null, null);
+ }
+
+ public static Project createProject(final String name, final String description, final List<User> users,
+ final List<Task> tasks) {
+ final Project project = new Project(PROJECTS.size() + 1L, name, description);
+
+ project.setTasks(tasks == null ? new ArrayList<Task>() : tasks);
+ project.setUsers(users == null ? new ArrayList<User>() : users);
+ PROJECTS.put(project.getId(), project);
+
+ return project;
+ }
+
+ public static User createUser(final String name, final String email) {
+ return createUser(name, email, null, null);
+ }
+
+ public static User createUser(final String name, final String email, final List<Project> projects, final List<Task> tasks) {
+ final User user = new User(USERS.size() + 1L, name, email);
+
+ user.setProjects(projects == null ? new ArrayList<Project>() : projects);
+ user.setTasks(tasks == null ? new ArrayList<Task>() : tasks);
+ USERS.put(user.getId(), user);
+
+ return user;
+ }
+
+ public static Task createTask(final String name, final String description) {
+ return createTask(name, description, null, null);
+ }
+
+ public static Task createTask(final String name, final String description, final Project project, final User user) {
+ final Task task = new Task(TASKS.size() + 1L, name, description);
+
+ task.setProject(project);
+ task.setUser(user);
+ TASKS.put(task.getId(), task);
+
+ return task;
+ }
+
+ public static Project getProject(final Long id) {
+ return PROJECTS.get(id);
+ }
+
+ public static User getUser(final Long id) {
+ return USERS.get(id);
+ }
+
+ public static Task getTask(final Long id) {
+ return TASKS.get(id);
+ }
+
+ public static List<Project> getProjects() {
+ return new ArrayList<>(PROJECTS.values());
+ }
+
+ public static List<User> getUsers() {
+ return new ArrayList<>(USERS.values());
+ }
+
+ public static List<Task> getTasks() {
+ return new ArrayList<>(TASKS.values());
+ }
+
+ /**
+ * Prevent instantiation.
+ */
+ private EntityStore() {
+ }
+}
diff --git a/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/domain/Project.java b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/domain/Project.java
new file mode 100644
index 0000000..8533ea9
--- /dev/null
+++ b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/domain/Project.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.domain;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.glassfish.jersey.examples.entityfiltering.filtering.ProjectDetailedView;
+
+/**
+ * Project entity class. Fields {@code tasks} and {@code users} are available only in detailed view (defined via
+ * {@link ProjectDetailedView}).
+ *
+ * @author Michal Gajdos
+ */
+@SuppressWarnings({"JavaDoc", "UnusedDeclaration"})
+@XmlRootElement
+public class Project {
+
+ private Long id;
+
+ private String name;
+
+ private String description;
+
+ @ProjectDetailedView
+ private List<Task> tasks;
+
+ @ProjectDetailedView
+ private List<User> users;
+
+ public Project() {
+ }
+
+ public Project(final Long id, final String name, final String description) {
+ this.id = id;
+ this.name = name;
+ this.description = description;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ public List<Task> getTasks() {
+ return tasks;
+ }
+
+ public void setTasks(final List<Task> tasks) {
+ this.tasks = tasks;
+ }
+
+ public List<User> getUsers() {
+ return users;
+ }
+
+ public void setUsers(final List<User> users) {
+ this.users = users;
+ }
+}
diff --git a/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/domain/Task.java b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/domain/Task.java
new file mode 100644
index 0000000..dd7d889
--- /dev/null
+++ b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/domain/Task.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.domain;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.glassfish.jersey.examples.entityfiltering.filtering.TaskDetailedView;
+
+/**
+ * Task entity class. Fields {@code project} and {@code user} are available only in detailed view (defined via
+ * {@link TaskDetailedView}).
+ *
+ * @author Michal Gajdos
+ */
+@SuppressWarnings({"UnusedDeclaration", "JavaDoc"})
+@XmlRootElement
+public class Task {
+
+ private Long id;
+
+ private String name;
+
+ private String description;
+
+ @TaskDetailedView
+ private Project project;
+
+ @TaskDetailedView
+ private User user;
+
+ public Task() {
+ }
+
+ public Task(final Long id, final String name, final String description) {
+ this.id = id;
+ this.name = name;
+ this.description = description;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ public Project getProject() {
+ return project;
+ }
+
+ public void setProject(final Project project) {
+ this.project = project;
+ }
+
+ public User getUser() {
+ return user;
+ }
+
+ public void setUser(final User user) {
+ this.user = user;
+ }
+}
diff --git a/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/domain/User.java b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/domain/User.java
new file mode 100644
index 0000000..5a2673f
--- /dev/null
+++ b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/domain/User.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.domain;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.glassfish.jersey.examples.entityfiltering.filtering.UserDetailedView;
+
+/**
+ * User entity class. Fields {@code projects} and {@code tasks} are available only in detailed view (defined via
+ * {@link UserDetailedView} on getters).
+ *
+ * @author Michal Gajdos
+ */
+@SuppressWarnings({"UnusedDeclaration", "JavaDoc"})
+@XmlRootElement
+public class User {
+
+ private Long id;
+
+ private String name;
+
+ private String email;
+
+ private List<Project> projects;
+
+ private List<Task> tasks;
+
+ public User() {
+ }
+
+ public User(final Long id, final String name, final String email) {
+ this.id = id;
+ this.name = name;
+ this.email = email;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(final String email) {
+ this.email = email;
+ }
+
+ @UserDetailedView
+ public List<Project> getProjects() {
+ return projects;
+ }
+
+ public void setProjects(final List<Project> projects) {
+ this.projects = projects;
+ }
+
+ @UserDetailedView
+ public List<Task> getTasks() {
+ return tasks;
+ }
+
+ public void setTasks(final List<Task> tasks) {
+ this.tasks = tasks;
+ }
+}
diff --git a/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/filtering/ProjectDetailedView.java b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/filtering/ProjectDetailedView.java
new file mode 100644
index 0000000..0264e9d
--- /dev/null
+++ b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/filtering/ProjectDetailedView.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.filtering;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.internal.inject.AnnotationLiteral;
+import org.glassfish.jersey.message.filtering.EntityFiltering;
+
+/**
+ * Entity-filtering annotation used to define detailed view on returned
+ * {@link org.glassfish.jersey.examples.entityfiltering.domain.Project} entities.
+ *
+ * @author Michal Gajdos
+ */
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@EntityFiltering
+public @interface ProjectDetailedView {
+
+ /**
+ * Factory class for creating instances of {@code ProjectDetailedView} annotation.
+ */
+ public static class Factory extends AnnotationLiteral<ProjectDetailedView> implements ProjectDetailedView {
+
+ private Factory() {
+ }
+
+ public static ProjectDetailedView get() {
+ return new Factory();
+ }
+ }
+}
diff --git a/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/filtering/TaskDetailedView.java b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/filtering/TaskDetailedView.java
new file mode 100644
index 0000000..293e809
--- /dev/null
+++ b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/filtering/TaskDetailedView.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.filtering;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.internal.inject.AnnotationLiteral;
+import org.glassfish.jersey.message.filtering.EntityFiltering;
+
+/**
+ * Entity-filtering annotation used to define detailed view on returned
+ * {@link org.glassfish.jersey.examples.entityfiltering.domain.Task} entities.
+ *
+ * @author Michal Gajdos
+ */
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@EntityFiltering
+public @interface TaskDetailedView {
+
+ /**
+ * Factory class for creating instances of {@code TaskDetailedView} annotation.
+ */
+ public static class Factory extends AnnotationLiteral<TaskDetailedView> implements TaskDetailedView {
+
+ private Factory() {
+ }
+
+ public static TaskDetailedView get() {
+ return new Factory();
+ }
+ }
+}
diff --git a/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/filtering/UserDetailedView.java b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/filtering/UserDetailedView.java
new file mode 100644
index 0000000..4af93cd
--- /dev/null
+++ b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/filtering/UserDetailedView.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.filtering;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.internal.inject.AnnotationLiteral;
+import org.glassfish.jersey.message.filtering.EntityFiltering;
+
+/**
+ * Entity-filtering annotation used to define detailed view on returned
+ * {@link org.glassfish.jersey.examples.entityfiltering.domain.User} entities.
+ *
+ * @author Michal Gajdos
+ */
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@EntityFiltering
+public @interface UserDetailedView {
+
+ /**
+ * Factory class for creating instances of {@code UserDetailedView} annotation.
+ */
+ public static class Factory extends AnnotationLiteral<UserDetailedView> implements UserDetailedView {
+
+ private Factory() {
+ }
+
+ public static UserDetailedView get() {
+ return new Factory();
+ }
+ }
+}
diff --git a/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/resource/ProjectsResource.java b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/resource/ProjectsResource.java
new file mode 100644
index 0000000..fe0ece6
--- /dev/null
+++ b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/resource/ProjectsResource.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.resource;
+
+import java.util.List;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+
+import org.glassfish.jersey.examples.entityfiltering.domain.EntityStore;
+import org.glassfish.jersey.examples.entityfiltering.domain.Project;
+import org.glassfish.jersey.examples.entityfiltering.filtering.ProjectDetailedView;
+
+/**
+ * Resource class for {@link Project projects}. Provides methods to retrieve projects in "default" view ({@link #getProjects()}
+ * and {@link #getProject(Long)}) and in "detailed" view ({@link #getDetailedProjects()} and {@link #getDetailedProject(Long)}).
+ * <p/>
+ * To reduce the number of methods while keeping the functionality of resource unchanged (support for both "default" and
+ * "detailed" view) see {@link UsersResource}.
+ *
+ * @author Michal Gajdos
+ * @see UsersResource
+ */
+@Path("projects")
+@Produces("application/json")
+public class ProjectsResource {
+
+ @GET
+ @Path("{id}")
+ public Project getProject(@PathParam("id") final Long id) {
+ return getDetailedProject(id);
+ }
+
+ @GET
+ public List<Project> getProjects() {
+ return getDetailedProjects();
+ }
+
+ @GET
+ @Path("detailed/{id}")
+ @ProjectDetailedView
+ public Project getDetailedProject(@PathParam("id") final Long id) {
+ return EntityStore.getProject(id);
+ }
+
+ @GET
+ @Path("detailed")
+ @ProjectDetailedView
+ public List<Project> getDetailedProjects() {
+ return EntityStore.getProjects();
+ }
+}
diff --git a/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/resource/TasksResource.java b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/resource/TasksResource.java
new file mode 100644
index 0000000..1b1f177
--- /dev/null
+++ b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/resource/TasksResource.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.resource;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.examples.entityfiltering.domain.EntityStore;
+import org.glassfish.jersey.examples.entityfiltering.domain.Task;
+import org.glassfish.jersey.examples.entityfiltering.filtering.TaskDetailedView;
+
+/**
+ * Resource class for {@link Task tasks}.
+ *
+ * @author Michal Gajdos
+ */
+@Path("tasks")
+@Produces("application/json")
+public class TasksResource {
+
+ @GET
+ public List<Task> getTasks() {
+ return getDetailedTasks();
+ }
+
+ @GET
+ @Path("detailed")
+ @TaskDetailedView
+ public List<Task> getDetailedTasks() {
+ return EntityStore.getTasks();
+ }
+
+ @GET
+ @Path("{id}")
+ public Response getTask(@PathParam("id") final Long id, @QueryParam("detailed") final boolean isDetailed) {
+ return Response
+ .ok()
+ .entity(EntityStore.getTask(id),
+ isDetailed ? new Annotation[] {TaskDetailedView.Factory.get()} : new Annotation[0])
+ .build();
+ }
+}
diff --git a/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/resource/UsersResource.java b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/resource/UsersResource.java
new file mode 100644
index 0000000..71d7646
--- /dev/null
+++ b/examples/entity-filtering/src/main/java/org/glassfish/jersey/examples/entityfiltering/resource/UsersResource.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering.resource;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.GenericEntity;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.examples.entityfiltering.domain.EntityStore;
+import org.glassfish.jersey.examples.entityfiltering.domain.User;
+import org.glassfish.jersey.examples.entityfiltering.filtering.UserDetailedView;
+
+/**
+ * Resource class for {@link User users}. Provides combined methods to retrieve users in "default" view and "detailed" view.
+ * Detailed view can be triggered by appending {@code detailed} query param to the URI.
+ * <p/>
+ * To see the resource methods expanded refer to the {@link ProjectsResource}.
+ *
+ * @author Michal Gajdos
+ * @see ProjectsResource
+ */
+@Path("users")
+@Produces("application/json")
+public class UsersResource {
+
+ @GET
+ public Response getUsers(@QueryParam("detailed") final boolean isDetailed) {
+ return Response
+ .ok()
+ .entity(new GenericEntity<List<User>>(EntityStore.getUsers()) {},
+ isDetailed ? new Annotation[]{UserDetailedView.Factory.get()} : new Annotation[0])
+ .build();
+ }
+
+ @GET
+ @Path("{id}")
+ public Response getUser(@PathParam("id") final Long id, @QueryParam("detailed") final boolean isDetailed) {
+ return Response
+ .ok()
+ .entity(EntityStore.getUser(id),
+ isDetailed ? new Annotation[]{UserDetailedView.Factory.get()} : new Annotation[0])
+ .build();
+ }
+}
diff --git a/examples/entity-filtering/src/test/java/org/glassfish/jersey/examples/entityfiltering/ProjectsResourceTest.java b/examples/entity-filtering/src/test/java/org/glassfish/jersey/examples/entityfiltering/ProjectsResourceTest.java
new file mode 100644
index 0000000..5d51237
--- /dev/null
+++ b/examples/entity-filtering/src/test/java/org/glassfish/jersey/examples/entityfiltering/ProjectsResourceTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.examples.entityfiltering.domain.Project;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.message.filtering.EntityFilteringFeature;
+import org.glassfish.jersey.moxy.json.MoxyJsonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * {@link org.glassfish.jersey.examples.entityfiltering.resource.UsersResource} unit tests.
+ *
+ * @author Michal Gajdos
+ */
+@RunWith(Parameterized.class)
+public class ProjectsResourceTest extends JerseyTest {
+
+ @Parameterized.Parameters(name = "Provider: {0}")
+ public static Iterable<Class[]> providers() {
+ return Arrays.asList(new Class[][]{{MoxyJsonFeature.class}, {JacksonFeature.class}});
+ }
+
+ public ProjectsResourceTest(final Class<Feature> filteringProvider) {
+ super(new ResourceConfig(EntityFilteringFeature.class)
+ .packages("org.glassfish.jersey.examples.entityfiltering.resource")
+ .register(filteringProvider));
+
+ enable(TestProperties.DUMP_ENTITY);
+ enable(TestProperties.LOG_TRAFFIC);
+ }
+
+ @Test
+ public void testProjects() throws Exception {
+ for (final Project project : target("projects").request().get(new GenericType<List<Project>>() {})) {
+ testProject(project, false);
+ }
+ }
+
+ @Test
+ public void testProject() throws Exception {
+ testProject(target("projects").path("1").request().get(Project.class), false);
+ }
+
+ @Test
+ public void testDetailedProjects() throws Exception {
+ for (final Project project : target("projects/detailed").request().get(new GenericType<List<Project>>() {})) {
+ testProject(project, true);
+ }
+ }
+
+ @Test
+ public void testDetailedProject() throws Exception {
+ testProject(target("projects/detailed").path("1").request().get(Project.class), true);
+ }
+
+ private void testProject(final Project project, final boolean isDetailed) {
+ // Following properties should be in every returned project.
+ assertThat(project.getId(), notNullValue());
+ assertThat(project.getName(), notNullValue());
+ assertThat(project.getDescription(), notNullValue());
+
+ // Tasks and users should be only in "detailed" view.
+ if (!isDetailed) {
+ assertThat("Users present in non-detailed project view", project.getUsers(), nullValue());
+ assertThat("Tasks present in non-detailed project view", project.getTasks(), nullValue());
+ } else {
+ assertThat("Users not present in detailed project view", project.getUsers(), notNullValue());
+ assertThat("Tasks not present in detailed project view", project.getTasks(), notNullValue());
+ }
+ }
+}
diff --git a/examples/entity-filtering/src/test/java/org/glassfish/jersey/examples/entityfiltering/TaskResourceTest.java b/examples/entity-filtering/src/test/java/org/glassfish/jersey/examples/entityfiltering/TaskResourceTest.java
new file mode 100644
index 0000000..c7171ac
--- /dev/null
+++ b/examples/entity-filtering/src/test/java/org/glassfish/jersey/examples/entityfiltering/TaskResourceTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.examples.entityfiltering.domain.Task;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.message.filtering.EntityFilteringFeature;
+import org.glassfish.jersey.moxy.json.MoxyJsonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * {@link org.glassfish.jersey.examples.entityfiltering.resource.TasksResource} unit tests.
+ *
+ * @author Michal Gajdos
+ */
+@RunWith(Parameterized.class)
+public class TaskResourceTest extends JerseyTest {
+
+ @Parameterized.Parameters(name = "Provider: {0}")
+ public static Iterable<Class[]> providers() {
+ return Arrays.asList(new Class[][]{{MoxyJsonFeature.class}, {JacksonFeature.class}});
+ }
+
+ public TaskResourceTest(final Class<Feature> filteringProvider) {
+ super(new ResourceConfig(EntityFilteringFeature.class)
+ .packages("org.glassfish.jersey.examples.entityfiltering.resource")
+ .register(filteringProvider));
+
+ enable(TestProperties.DUMP_ENTITY);
+ enable(TestProperties.LOG_TRAFFIC);
+ }
+
+ @Test
+ public void testTasks() throws Exception {
+ for (final Task task : target("tasks").request().get(new GenericType<List<Task>>() {})) {
+ testTask(task, false);
+ }
+ }
+
+ @Test
+ public void testTask() throws Exception {
+ testTask(target("tasks").path("1").request().get(Task.class), false);
+ }
+
+ @Test
+ public void testDetailedTasks() throws Exception {
+ for (final Task task : target("tasks").path("detailed").request().get(new GenericType<List<Task>>() {})) {
+ testTask(task, true);
+ }
+ }
+
+ @Test
+ public void testDetailedTask() throws Exception {
+ testTask(target("tasks").path("1").queryParam("detailed", true).request().get(Task.class), true);
+ }
+
+ private void testTask(final Task task, final boolean isDetailed) {
+ // Following properties should be in every returned task.
+ assertThat(task.getId(), notNullValue());
+ assertThat(task.getName(), notNullValue());
+ assertThat(task.getDescription(), notNullValue());
+
+ // Tasks and tasks should be in "detailed" view.
+ if (!isDetailed) {
+ assertThat(task.getProject(), nullValue());
+ assertThat(task.getUser(), nullValue());
+ } else {
+ assertThat(task.getProject(), notNullValue());
+ assertThat(task.getUser(), notNullValue());
+ }
+ }
+}
diff --git a/examples/entity-filtering/src/test/java/org/glassfish/jersey/examples/entityfiltering/UsersResourceTest.java b/examples/entity-filtering/src/test/java/org/glassfish/jersey/examples/entityfiltering/UsersResourceTest.java
new file mode 100644
index 0000000..e85f779
--- /dev/null
+++ b/examples/entity-filtering/src/test/java/org/glassfish/jersey/examples/entityfiltering/UsersResourceTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.entityfiltering;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.examples.entityfiltering.domain.User;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.message.filtering.EntityFilteringFeature;
+import org.glassfish.jersey.moxy.json.MoxyJsonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+/**
+ * {@link org.glassfish.jersey.examples.entityfiltering.resource.UsersResource} unit tests.
+ *
+ * @author Michal Gajdos
+ */
+@RunWith(Parameterized.class)
+public class UsersResourceTest extends JerseyTest {
+
+ @Parameterized.Parameters(name = "Provider: {0}")
+ public static Iterable<Class[]> providers() {
+ return Arrays.asList(new Class[][]{{MoxyJsonFeature.class}, {JacksonFeature.class}});
+ }
+
+ public UsersResourceTest(final Class<Feature> filteringProvider) {
+ super(new ResourceConfig(EntityFilteringFeature.class)
+ .packages("org.glassfish.jersey.examples.entityfiltering.resource")
+ .register(filteringProvider));
+
+ enable(TestProperties.DUMP_ENTITY);
+ enable(TestProperties.LOG_TRAFFIC);
+ }
+
+ @Test
+ public void testUsers() throws Exception {
+ for (final User user : target("users").request().get(new GenericType<List<User>>() {})) {
+ testUser(user, false);
+ }
+ }
+
+ @Test
+ public void testUser() throws Exception {
+ testUser(target("users").path("1").request().get(User.class), false);
+ }
+
+ @Test
+ public void testDetailedUsers() throws Exception {
+ for (final User user : target("users").queryParam("detailed", true).request().get(new GenericType<List<User>>() {})) {
+ testUser(user, true);
+ }
+ }
+
+ @Test
+ public void testDetailedUser() throws Exception {
+ testUser(target("users").path("1").queryParam("detailed", true).request().get(User.class), true);
+ }
+
+ private void testUser(final User user, final boolean isDetailed) {
+ // Following properties should be in every returned user.
+ assertThat(user.getId(), notNullValue());
+ assertThat(user.getName(), notNullValue());
+ assertThat(user.getEmail(), notNullValue());
+
+ // Tasks and users should be in "detailed" view.
+ if (!isDetailed) {
+ assertThat(user.getProjects(), nullValue());
+ assertThat(user.getTasks(), nullValue());
+ } else {
+ assertThat(user.getProjects(), notNullValue());
+ assertThat(user.getTasks(), notNullValue());
+ }
+ }
+}
diff --git a/examples/etc/gf-project-src-pom.xsl b/examples/etc/gf-project-src-pom.xsl
new file mode 100644
index 0000000..50df08f
--- /dev/null
+++ b/examples/etc/gf-project-src-pom.xsl
@@ -0,0 +1,211 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:pom="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ version="1.0">
+
+ <xsl:output method="xml" indent="yes" />
+
+ <xsl:template match="/">
+ <xsl:apply-templates />
+ </xsl:template>
+
+ <xsl:template
+ match="pom:dependencies/pom:dependency[pom:groupId='org.glassfish.jersey.core'
+ or pom:groupId='org.glassfish.jersey.containers'
+ or pom:groupId='org.glassfish.jersey.ext.cdi'
+ or pom:groupId='org.glassfish.jersey.media'
+ or pom:artifactId='jersey-wadl-doclet'
+ or pom:artifactId='jersey-mvc-jsp'
+ or pom:artifactId='jersey-bean-validation'
+ or pom:groupId='com.sun.xml.bind'
+ or pom:groupId='org.codehaus.jettison'
+ or pom:groupId='javax.annotation'
+ or pom:groupId='javax.enterprise'
+ or pom:groupId='javax.servlet'
+ or pom:groupId='javax.ws.rs']/pom:scope[text()!=test]">
+ <scope>provided</scope>
+ </xsl:template>
+
+ <xsl:template
+ match="pom:dependencies/pom:dependency[pom:groupId='org.glassfish.jersey.core'
+ or pom:groupId='org.glassfish.jersey.containers'
+ or pom:groupId='org.glassfish.jersey.ext.cdi'
+ or pom:groupId='org.glassfish.jersey.media'
+ or pom:artifactId='jersey-wadl-doclet'
+ or pom:artifactId='jersey-mvc-jsp'
+ or pom:artifactId='jersey-bean-validation'
+ or pom:groupId='com.sun.xml.bind'
+ or pom:groupId='javax.validation'
+ or pom:groupId='org.codehaus.jettison'
+ or pom:groupId='javax.annotation'
+ or pom:groupId='javax.enterprise'
+ or pom:groupId='javax.servlet'
+ or pom:groupId='javax.ws.rs']">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:scope)=0">
+ <scope>provided</scope>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template
+ match="pom:dependencies/pom:dependency[pom:artifactId='jersey-mvc-freemarker']">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <exclusions>
+ <exclusion>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-mvc</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </xsl:copy>
+ </xsl:template>
+
+ <!-- There is problem to run Spring example on GF - https://java.net/jira/browse/JERSEY-2032
+ <xsl:template
+ match="pom:dependencies/pom:dependency[pom:artifactId='jersey-spring3']">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <exclusions>
+ <exclusion>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ </exclusion>
+
+
+ <exclusion>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ </exclusion>
+
+ <exclusion>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ </exclusion>
+
+ <exclusion>
+ <groupId>org.glassfish.hk2</groupId>
+ <artifactId>hk2</artifactId>
+ </exclusion>
+
+ <exclusion>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </xsl:copy>
+ </xsl:template>
+ -->
+
+ <xsl:template match="pom:dependencies">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:dependency[pom:artifactId='jersey-container-servlet-core'])=0">
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </xsl:if>
+ <xsl:if test="count(pom:dependency[pom:artifactId='jersey-mvc-freemarker'])=1">
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-mvc</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="pom:project">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:dependencies)=0">
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <!-- remove <packagingExcludes>WEB-INF/glassfish-web.xml</packagingExcludes>
+ as this file is required in Glassfish bundle since <class-loader>
+ is defined in it -->
+ <xsl:template match="pom:plugin[pom:artifactId='maven-war-plugin']/pom:configuration[pom:packagingExcludes]">
+ </xsl:template>
+
+ <!--build war even if web.xml is missing as it's not required,
+ <packagingIncludes> defaults to 'all' so it includes
+ <packagingIncludes>WEB-INF/glassfish-web.xml</packagingIncludes>
+ to pick up <class-loader> -->
+ <xsl:template match="pom:plugin[pom:artifactId='maven-war-plugin']">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:configuration)=1">
+ <configuration>
+ <failOnMissingWebXml>false</failOnMissingWebXml>
+ </configuration>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <!-- remove examples-source-zip profile -->
+ <xsl:template match="pom:profile/pom:plugins/pom:plugin[pom:id='examples-source-zip']">
+ </xsl:template>
+
+ <!--<!– remove xslt-maven-plugin –>-->
+ <!--<xsl:template match="pom:plugin[pom:artifactId='xml-maven-plugin']">-->
+ <!--</xsl:template>-->
+
+ <!--<!– remove maven-assembly-plugin –>-->
+ <!--<xsl:template match="pom:plugin[pom:artifactId='maven-assembly-plugin']">-->
+ <!--</xsl:template>-->
+
+ <!-- remove maven-jetty-plugin -->
+ <xsl:template match="pom:plugin[pom:artifactId='maven-jetty-plugin']">
+ </xsl:template>
+
+ <!-- remove jetty-maven-plugin -->
+ <xsl:template match="pom:plugin[pom:artifactId='jetty-maven-plugin']">
+ </xsl:template>
+
+ <!-- remove failsafe plugin (integration testing not possible without jetty/other container) -->
+ <xsl:template match="pom:plugin[pom:artifactId='maven-failsafe-plugin']">
+ </xsl:template>
+
+ <xsl:template match="comment()">
+ <xsl:comment>
+ <xsl:value-of select="." />
+ </xsl:comment>
+ </xsl:template>
+
+ <xsl:template match="*">
+ <xsl:copy>
+ <xsl:apply-templates />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/examples/etc/wls-project-src-pom.xsl b/examples/etc/wls-project-src-pom.xsl
new file mode 100644
index 0000000..6116b5b
--- /dev/null
+++ b/examples/etc/wls-project-src-pom.xsl
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:pom="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ version="1.0">
+
+ <xsl:output method="xml" indent="yes" />
+
+ <xsl:template match="/">
+ <xsl:apply-templates />
+ </xsl:template>
+
+ <xsl:template
+ match="pom:dependencies/pom:dependency[pom:groupId='org.glassfish.jersey.core'
+ or pom:groupId='org.glassfish.jersey.containers'
+ or pom:artifactId='jersey-bean-validation'
+ or pom:artifactId='jersey-media-json-jackson'
+ or pom:artifactId='jersey-media-json-jettison'
+ or pom:artifactId='jersey-media-moxy'
+ or pom:artifactId='jersey-media-multipart'
+ or pom:artifactId='jersey-media-sse'
+ or pom:groupId='com.sun.xml.bind'
+ or pom:groupId='javax.servlet']/pom:scope[text()!=test]">
+ <scope>provided</scope>
+ </xsl:template>
+
+ <xsl:template
+ match="pom:dependencies/pom:dependency[pom:groupId='org.glassfish.jersey.core'
+ or pom:groupId='org.glassfish.jersey.containers'
+ or pom:artifactId='jersey-bean-validation'
+ or pom:artifactId='jersey-media-json-jackson'
+ or pom:artifactId='jersey-media-json-jettison'
+ or pom:artifactId='jersey-media-moxy'
+ or pom:artifactId='jersey-media-multipart'
+ or pom:artifactId='jersey-media-sse'
+ or pom:groupId='com.sun.xml.bind'
+ or pom:groupId='javax.servlet']">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:scope)=0">
+ <scope>provided</scope>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template
+ match="pom:dependencies/pom:dependency[pom:artifactId='jersey-mvc-jsp'
+ or pom:artifactId='jersey-mvc-freemarker'
+ or pom:artifactId='jersey-media-json-processing']/pom:scope[text()!=test]">
+ <exclusions>
+ <exclusion>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </xsl:template>
+
+ <xsl:template
+ match="pom:dependencies/pom:dependency[pom:artifactId='jersey-mvc-jsp'
+ or pom:artifactId='jersey-mvc-freemarker'
+ or pom:artifactId='jersey-media-json-processing']">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:scope)=0">
+ <exclusions>
+ <exclusion>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="pom:dependencies">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:dependency[pom:artifactId='jersey-container-servlet-core'])=0">
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="pom:project">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:dependencies)=0">
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <!-- remove <packagingExcludes>WEB-INF/glassfish-web.xml</packagingExcludes>
+ as this file is required in Glassfish bundle since <class-loader>
+ is defined in it -->
+ <xsl:template match="pom:plugin[pom:artifactId='maven-war-plugin']/pom:configuration[pom:packagingExcludes]">
+ </xsl:template>
+
+ <!--build war even if web.xml is missing as it's not required,
+ <packagingIncludes> defaults to 'all' so it includes
+ <packagingIncludes>WEB-INF/glassfish-web.xml</packagingIncludes>
+ to pick up <class-loader> -->
+ <xsl:template match="pom:plugin[pom:artifactId='maven-war-plugin']">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:configuration)=1">
+ <configuration>
+ <failOnMissingWebXml>false</failOnMissingWebXml>
+ </configuration>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <!-- remove examples-source-zip profile -->
+ <xsl:template match="pom:profile/pom:plugins/pom:plugin[pom:id='examples-source-zip']">
+ </xsl:template>
+
+ <!--<!– remove xslt-maven-plugin –>-->
+ <!--<xsl:template match="pom:plugin[pom:artifactId='xml-maven-plugin']">-->
+ <!--</xsl:template>-->
+
+ <!--<!– remove maven-assembly-plugin –>-->
+ <!--<xsl:template match="pom:plugin[pom:artifactId='maven-assembly-plugin']">-->
+ <!--</xsl:template>-->
+
+ <!-- remove maven-jetty-plugin -->
+ <xsl:template match="pom:plugin[pom:artifactId='maven-jetty-plugin']">
+ </xsl:template>
+
+ <!-- remove jetty-maven-plugin -->
+ <xsl:template match="pom:plugin[pom:artifactId='jetty-maven-plugin']">
+ </xsl:template>
+
+ <xsl:template match="comment()">
+ <xsl:comment>
+ <xsl:value-of select="." />
+ </xsl:comment>
+ </xsl:template>
+
+ <xsl:template match="*">
+ <xsl:copy>
+ <xsl:apply-templates />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/examples/etc/wls-project-src-web.xsl b/examples/etc/wls-project-src-web.xsl
new file mode 100644
index 0000000..46009cf
--- /dev/null
+++ b/examples/etc/wls-project-src-web.xsl
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:web="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ version="1.0">
+
+ <xsl:template match="node()|@*">
+ <xsl:copy>
+ <xsl:apply-templates select="node()|@*"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <!-- fixes the hardcoded port in managed-client-webapp example's web.xml -->
+ <xsl:template match="//web:web-app/web:servlet/web:init-param[web:param-name='org.glassfish.jersey.examples.managedclient.ClientA.baseUri']/web:param-value">
+ <xsl:copy>
+ <xsl:text>http://localhost:7001/managed-client-webapp/internal</xsl:text>
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/examples/etc/wls1213-project-src-pom.xsl b/examples/etc/wls1213-project-src-pom.xsl
new file mode 100644
index 0000000..40b63b5
--- /dev/null
+++ b/examples/etc/wls1213-project-src-pom.xsl
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:pom="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ version="1.0">
+
+ <xsl:output method="xml" indent="yes" />
+
+ <xsl:template match="/">
+ <xsl:apply-templates />
+ </xsl:template>
+
+ <xsl:template
+ match="pom:dependencies/pom:dependency[pom:groupId='org.glassfish.jersey.core'
+ or pom:groupId='org.glassfish.jersey.containers'
+ or pom:artifactId='jersey-media-json-jackson'
+ or pom:artifactId='jersey-media-json-jettison'
+ or pom:artifactId='jersey-media-moxy'
+ or pom:artifactId='jersey-media-multipart'
+ or pom:artifactId='jersey-media-sse'
+ or pom:groupId='com.sun.xml.bind'
+ or pom:groupId='javax.servlet']/pom:scope[text()!=test]">
+ <scope>provided</scope>
+ </xsl:template>
+
+ <xsl:template
+ match="pom:dependencies/pom:dependency[pom:groupId='org.glassfish.jersey.core'
+ or pom:groupId='org.glassfish.jersey.containers'
+ or pom:artifactId='jersey-media-json-jackson'
+ or pom:artifactId='jersey-media-json-jettison'
+ or pom:artifactId='jersey-media-moxy'
+ or pom:artifactId='jersey-media-multipart'
+ or pom:artifactId='jersey-media-sse'
+ or pom:groupId='com.sun.xml.bind'
+ or pom:groupId='javax.servlet']">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:scope)=0">
+ <scope>provided</scope>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template
+ match="pom:dependencies/pom:dependency[pom:artifactId='jersey-bean-validation'
+ or pom:artifactId='jersey-mvc-jsp'
+ or pom:artifactId='jersey-mvc-freemarker'
+ or pom:artifactId='jersey-media-json-processing']/pom:scope[text()!=test]">
+ <exclusions>
+ <exclusion>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </xsl:template>
+
+ <xsl:template
+ match="pom:dependencies/pom:dependency[pom:artifactId='jersey-bean-validation'
+ or pom:artifactId='jersey-mvc-jsp'
+ or pom:artifactId='jersey-mvc-freemarker'
+ or pom:artifactId='jersey-media-json-processing']">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:scope)=0">
+ <exclusions>
+ <exclusion>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="pom:dependencies">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:dependency[pom:artifactId='jersey-container-servlet-core'])=0">
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="pom:project">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:dependencies)=0">
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <!-- remove <packagingExcludes>WEB-INF/glassfish-web.xml</packagingExcludes>
+ as this file is required in Glassfish bundle since <class-loader>
+ is defined in it -->
+ <xsl:template match="pom:plugin[pom:artifactId='maven-war-plugin']/pom:configuration[pom:packagingExcludes]">
+ </xsl:template>
+
+ <!--build war even if web.xml is missing as it's not required,
+ <packagingIncludes> defaults to 'all' so it includes
+ <packagingIncludes>WEB-INF/glassfish-web.xml</packagingIncludes>
+ to pick up <class-loader> -->
+ <xsl:template match="pom:plugin[pom:artifactId='maven-war-plugin']">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <xsl:if test="count(pom:configuration)=1">
+ <configuration>
+ <failOnMissingWebXml>false</failOnMissingWebXml>
+ </configuration>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:template>
+
+ <!-- remove examples-source-zip profile -->
+ <xsl:template match="pom:profile/pom:plugins/pom:plugin[pom:id='examples-source-zip']">
+ </xsl:template>
+
+ <!--<!– remove xslt-maven-plugin –>-->
+ <!--<xsl:template match="pom:plugin[pom:artifactId='xml-maven-plugin']">-->
+ <!--</xsl:template>-->
+
+ <!--<!– remove maven-assembly-plugin –>-->
+ <!--<xsl:template match="pom:plugin[pom:artifactId='maven-assembly-plugin']">-->
+ <!--</xsl:template>-->
+
+ <!-- remove maven-jetty-plugin -->
+ <xsl:template match="pom:plugin[pom:artifactId='maven-jetty-plugin']">
+ </xsl:template>
+
+ <!-- remove jetty-maven-plugin -->
+ <xsl:template match="pom:plugin[pom:artifactId='jetty-maven-plugin']">
+ </xsl:template>
+
+ <xsl:template match="comment()">
+ <xsl:comment>
+ <xsl:value-of select="." />
+ </xsl:comment>
+ </xsl:template>
+
+ <xsl:template match="*">
+ <xsl:copy>
+ <xsl:apply-templates />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/examples/etc/wls1213-project-src-web.xsl b/examples/etc/wls1213-project-src-web.xsl
new file mode 100644
index 0000000..c241e7b
--- /dev/null
+++ b/examples/etc/wls1213-project-src-web.xsl
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:web="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ version="1.0">
+
+ <xsl:template match="node()|@*">
+ <xsl:copy>
+ <xsl:apply-templates select="node()|@*"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <!-- fixes the hardcoded port in managed-client-webapp example's web.xml -->
+ <xsl:template match="//web:web-app/web:servlet/web:init-param[web:param-name='org.glassfish.jersey.examples.managedclient.ClientA.baseUri']/web:param-value">
+ <xsl:copy>
+ <xsl:text>http://localhost:7001/managed-client-webapp/internal</xsl:text>
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/examples/etc/wls1213/README-WLS.html b/examples/etc/wls1213/README-WLS.html
new file mode 100644
index 0000000..be6b9b5
--- /dev/null
+++ b/examples/etc/wls1213/README-WLS.html
@@ -0,0 +1,78 @@
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!DOCTYPE html>
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <title>WLS 12.1.3 Shared Library installation steps.</title>
+
+ <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/css/bootstrap.min.css">
+ </head>
+
+ <body>
+ <div class="container">
+ <div class="row">
+ <h1>Weblogic 12.1.3 Shared Library Installation Steps</h1>
+
+ <p>
+ Before deploying an JAX-RS 2.0 application into WLS 12.1.3 you need to install (deploy) the JAX-RS 2.0 shared
+ library that provides necessary dependencies and configuration. This library is located at
+ <code>MW_HOME\wlserver\common\deployable-libraries\jax-rs-2.0.war</code>.
+ </p>
+ <p>
+ There are two ways, how you can deploy this library:
+ <ul>
+ <li>Using WLS console</li>
+ <li>Using WLST</li>
+ </ul>
+ </p>
+
+ <h2>Deploying shared library using WLS console</h2>
+
+ <ol>
+ <li>Log into WLS console at <code>http://<host>:<port>/console</code> (e.g. <code>http://localhost:7001/console</code>)</li>
+ <li>Click on <em>Deployments</em></li>
+ <li>Find Shared Library WAR (located at <code>MW_HOME\wlserver\common\deployable-libraries\jax-rs-2.0.war</code>)</li>
+ <li>Click on <em>Next</em></li>
+ <li>Choose <em>Deploy as Library</em></li>
+ <li>Click on <em>Next</em></li>
+ <li>Click on <em>Finish</em></li>
+ </ol>
+
+ <h2>Deploying shared library using WLST</h2>
+
+ <p>Lets assume that WLS is running and your domain is located in <code>MW_HOME</code> (<code>MW_HOME\domain</code>).</p>
+
+ <p>Setup your shell and enter the WLST console:</p>
+
+<pre>
+> # bash prompt
+> cd $MW_HOME\domain
+> . ./bin/setDomainEnv.sh
+> java weblogic.WLST
+</pre>
+
+ <p>Deploy the library:</p>
+
+<pre>
+> # WLST console
+> connect('user', 'pass', 'host')
+> deploy('jax-rs', '../wlserver/common/deployable-libraries/jax-rs-2.0.war', libraryModule='true')
+> exit()
+</pre>
+
+
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/examples/etc/wls1213/src/main/webapp/WEB-INF/weblogic.xml b/examples/etc/wls1213/src/main/webapp/WEB-INF/weblogic.xml
new file mode 100644
index 0000000..61c11c0
--- /dev/null
+++ b/examples/etc/wls1213/src/main/webapp/WEB-INF/weblogic.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<wls:weblogic-web-app xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.0/weblogic-web-app.xsd">
+
+ <wls:library-ref>
+ <wls:library-name>jax-rs</wls:library-name>
+ <wls:specification-version>2.0</wls:specification-version>
+ <wls:exact-match>true</wls:exact-match>
+ </wls:library-ref>
+</wls:weblogic-web-app>
diff --git a/examples/exception-mapping/README.MD b/examples/exception-mapping/README.MD
new file mode 100644
index 0000000..613624d
--- /dev/null
+++ b/examples/exception-mapping/README.MD
@@ -0,0 +1,49 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Exception Mapping Example
+=========================
+
+This example demonstrates how to create a custom exception mapping. There are described several common use-cases such as
+`WebApplicationException`, custom exceptions and an exception mapper inheritance.
+
+The full description how to handle exception and map them to Response object can be found in Jersey User Guide, chapter
+[WebApplicationException and Mapping Exceptions to Responses](https://jersey.java.net/documentation/latest/representations.html#d0e6567).
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | HTTP methods | Allowed Values | Description
+----------------------------------------- | ------------- |-------------------------------------------- | ----------------
+**_/exception_** | GET | --- | returns "ping!"
+**_/exception/webapplication_entity_** | POST | two numbers delimited by colons e.g. 1:201 | not handled by `WebApplicationExceptionMapper` (already has an entity in the exception)
+**_/exception/webapplication_noentity_** | POST | e.g. 1:201 | handled by `WebApplicationExceptionMapper`
+**_/exception/my_** | POST | e.g. 1:201 | handled by `MyExceptionMapper`
+**_/exception/mysub_** | POST | e.g. 1:201 | handled by `MySubExceptionMapper`
+**_/exception/mysubsub_** | POST | e.g. 1:201 | handled by `MySubExceptionMapper`
+**_/exception/request_exception_** | POST | String "Request Exception" | not reached a resource method, processed by `ContainerRequestFilter`
+**_/exception/response_exception_** | GET | --- | response handled and changed by `ContainerResponseFilter`
+
+
+Application is based on Grizzly container (see `App`). Everything needed
+(resources/providers) is registered in `ExceptionResource` and custom exceptions
+along with mappers can be found in `Exceptions` class.
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
+
+- <http://localhost:8080/base/exception>
+- <http://localhost:8080/base/exception/response_exception>
diff --git a/examples/exception-mapping/pom.xml b/examples/exception-mapping/pom.xml
new file mode 100644
index 0000000..68f418d
--- /dev/null
+++ b/examples/exception-mapping/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>exception-mapping</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-exception-mapping</name>
+
+ <description>Jersey example showing exception mappers in action.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.exception.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/App.java b/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/App.java
new file mode 100644
index 0000000..b520eda
--- /dev/null
+++ b/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/App.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.exception;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Hello world!
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/base/");
+ public static final String ROOT_PATH = "exception";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Exception Mapping\" Jersey Example App");
+
+ final ResourceConfig resourceConfig = new ResourceConfig(
+ ExceptionResource.class,
+ ExceptionResource.MyResponseFilter.class,
+ ExceptionResource.WebApplicationExceptionFilter.class,
+ Exceptions.MyExceptionMapper.class,
+ Exceptions.MySubExceptionMapper.class,
+ Exceptions.WebApplicationExceptionMapper.class);
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format(
+ "Application started.%n"
+ + "Try out %s%s%n"
+ + "Stop the application using CTRL+C",
+ BASE_URI, ROOT_PATH));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+}
diff --git a/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/ExceptionResource.java b/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/ExceptionResource.java
new file mode 100644
index 0000000..e4cfe74
--- /dev/null
+++ b/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/ExceptionResource.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.exception;
+
+import java.io.IOException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+import org.glassfish.jersey.examples.exception.Exceptions.MyException;
+import org.glassfish.jersey.examples.exception.Exceptions.MySubException;
+import org.glassfish.jersey.examples.exception.Exceptions.MySubSubException;
+import org.glassfish.jersey.server.ContainerRequest;
+
+/**
+ * ExceptionResource class.
+ *
+ * @author Santiago.PericasGeertsen at oracle.com
+ */
+@Path("exception")
+@Consumes("text/plain")
+@Produces("text/plain")
+public class ExceptionResource {
+
+ @Provider
+ static class MyResponseFilter implements ContainerResponseFilter {
+
+ @Override
+ public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
+ System.out.println("MyResponseFilter.postFilter() enter");
+ responseContext.setEntity(
+ responseContext.getEntity() + ":" + getClass().getSimpleName(), null, MediaType.TEXT_PLAIN_TYPE);
+ System.out.println("MyResponseFilter.postFilter() exit");
+ }
+ }
+
+ @Provider
+ static class WebApplicationExceptionFilter implements ContainerRequestFilter, ContainerResponseFilter {
+
+ @Override
+ public void filter(ContainerRequestContext context) throws IOException {
+ System.out.println("WebApplicationExceptionFilter.preFilter() enter");
+
+ String path = ((ContainerRequest) context).getRequestUri().getPath();
+ if (path.endsWith("request_exception") && context.hasEntity() && ((ContainerRequest) context)
+ .readEntity(String.class).equals("Request Exception")) {
+ throw new WebApplicationException(Response.Status.OK);
+ }
+ System.out.println("WebApplicationExceptionFilter.preFilter() exit");
+ }
+
+ @Override
+ public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
+ System.out.println("WebApplicationExceptionFilter.postFilter() enter");
+ if (responseContext.hasEntity() && responseContext.getEntity().equals("Response Exception")) {
+ throw new WebApplicationException(Response.Status.OK);
+ }
+ System.out.println("WebApplicationExceptionFilter.postFilter() exit");
+ }
+ }
+
+ @GET
+ public String pingMe() {
+ return "ping!";
+ }
+
+ @POST
+ @Path("webapplication_entity")
+ public String testWebApplicationExceptionEntity(String s) {
+ String[] tokens = s.split(":");
+ assert tokens.length == 2;
+ int statusCode = Integer.valueOf(tokens[1]);
+ Response r = Response.status(statusCode).entity(s).build();
+ throw new WebApplicationException(r);
+ }
+
+ @POST
+ @Path("webapplication_noentity")
+ public String testWebApplicationExceptionNoEntity(String s) {
+ String[] tokens = s.split(":");
+ assert tokens.length == 2;
+ int statusCode = Integer.valueOf(tokens[1]);
+ Response r = Response.status(statusCode).build();
+ throw new WebApplicationException(r);
+ }
+
+ @POST
+ @Path("my")
+ public String testMyException(String s) {
+ String[] tokens = s.split(":");
+ assert tokens.length == 2;
+ int statusCode = Integer.valueOf(tokens[1]);
+ Response r = Response.status(statusCode).build();
+ throw new MyException(r);
+ }
+
+ @POST
+ @Path("mysub")
+ public String testMySubException(String s) {
+ String[] tokens = s.split(":");
+ assert tokens.length == 2;
+ int statusCode = Integer.valueOf(tokens[1]);
+ Response r = Response.status(statusCode).build();
+ throw new MySubException(r);
+ }
+
+ @POST
+ @Path("mysubsub")
+ public String testMySubSubException(String s) {
+ String[] tokens = s.split(":");
+ assert tokens.length == 2;
+ int statusCode = Integer.valueOf(tokens[1]);
+ Response r = Response.status(statusCode).build();
+ throw new MySubSubException(r);
+ }
+
+ @POST
+ @Path("request_exception")
+ public String exceptionInRequestFilter() {
+ throw new InternalServerErrorException(); // should not reach here
+ }
+
+ @GET
+ @Path("response_exception")
+ public String exceptionInResponseFilter() {
+ return "Response Exception";
+ }
+}
diff --git a/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/Exceptions.java b/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/Exceptions.java
new file mode 100644
index 0000000..670bde8
--- /dev/null
+++ b/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/Exceptions.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.exception;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Exceptions class.
+ *
+ * @author Santiago.Pericas-Geertsen at oracle.com
+ */
+public class Exceptions {
+
+ // -- Exceptions
+ public static class MyException extends RuntimeException {
+
+ private Response response;
+
+ public MyException(Response response) {
+ this.response = response;
+ }
+
+ public Response getResponse() {
+ return response;
+ }
+ }
+
+ public static class MySubException extends MyException {
+
+ public MySubException(Response response) {
+ super(response);
+ }
+ }
+
+ public static class MySubSubException extends MySubException {
+
+ public MySubSubException(Response response) {
+ super(response);
+ }
+ }
+
+ // -- Exception Mappers
+ @Provider
+ public static class MyExceptionMapper implements ExceptionMapper<MyException> {
+
+ @Override
+ public Response toResponse(MyException exception) {
+ Response r = exception.getResponse();
+ return Response.status(r.getStatus()).entity(
+ "Code:" + r.getStatus() + ":" + getClass().getSimpleName()).build();
+ }
+ }
+
+ @Provider
+ public static class MySubExceptionMapper implements ExceptionMapper<MySubException> {
+
+ @Override
+ public Response toResponse(MySubException exception) {
+ Response r = exception.getResponse();
+ return Response.status(r.getStatus()).entity(
+ "Code:" + r.getStatus() + ":" + getClass().getSimpleName()).build();
+ }
+ }
+
+ @Provider
+ public static class WebApplicationExceptionMapper implements ExceptionMapper<WebApplicationException> {
+
+ @Override
+ public Response toResponse(WebApplicationException exception) {
+ Response r = exception.getResponse();
+ return Response.status(r.getStatus()).entity("Code:" + r.getStatus() + ":"
+ + getClass().getSimpleName()).build();
+ }
+ }
+}
diff --git a/examples/exception-mapping/src/test/java/org/glassfish/jersey/examples/exception/ExceptionMappingFilterTest.java b/examples/exception-mapping/src/test/java/org/glassfish/jersey/examples/exception/ExceptionMappingFilterTest.java
new file mode 100644
index 0000000..d509934
--- /dev/null
+++ b/examples/exception-mapping/src/test/java/org/glassfish/jersey/examples/exception/ExceptionMappingFilterTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.exception;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.glassfish.jersey.examples.exception.ExceptionResource.MyResponseFilter;
+import static org.glassfish.jersey.examples.exception.Exceptions.MyExceptionMapper;
+import static org.glassfish.jersey.examples.exception.Exceptions.MySubExceptionMapper;
+import static org.glassfish.jersey.examples.exception.Exceptions.WebApplicationExceptionMapper;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * ExceptionMappingFilterTest class.
+ *
+ * @author Santiago.PericasGeertsen at oracle.com
+ */
+public class ExceptionMappingFilterTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ // mvn test -Djersey.test.containerFactory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+ // mvn test -Djersey.test.containerFactory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+
+ final ResourceConfig resourceConfig = new ResourceConfig(
+ ExceptionResource.class,
+ MyResponseFilter.class,
+ ExceptionResource.WebApplicationExceptionFilter.class,
+ MyExceptionMapper.class,
+ MySubExceptionMapper.class,
+ WebApplicationExceptionMapper.class);
+
+ return resourceConfig;
+ }
+
+ /**
+ * Instructs request filter to throw a WebApplicationException which must be mapped
+ * to a response and processed through response pipeline.
+ */
+ @Test
+ public void testWebApplicationExceptionInRequestFilter() {
+ WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("request_exception").build());
+ Response r = t.request("text/plain").post(Entity.text("Request Exception"));
+ assertEquals(200, r.getStatus());
+ final String entity = r.readEntity(String.class);
+ System.out.println("entity = " + entity);
+ assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+ }
+
+ @Test
+ public void testWebApplicationExceptionInResponseFilter() {
+ WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("response_exception").build());
+ Response r = t.request("text/plain").get();
+ assertEquals(200, r.getStatus());
+ final String entity = r.readEntity(String.class);
+ System.out.println("entity = " + entity);
+ assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+ }
+}
diff --git a/examples/exception-mapping/src/test/java/org/glassfish/jersey/examples/exception/ExceptionMappingTest.java b/examples/exception-mapping/src/test/java/org/glassfish/jersey/examples/exception/ExceptionMappingTest.java
new file mode 100644
index 0000000..3166312
--- /dev/null
+++ b/examples/exception-mapping/src/test/java/org/glassfish/jersey/examples/exception/ExceptionMappingTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.exception;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.glassfish.jersey.examples.exception.ExceptionResource.MyResponseFilter;
+import static org.glassfish.jersey.examples.exception.Exceptions.MyExceptionMapper;
+import static org.glassfish.jersey.examples.exception.Exceptions.MySubExceptionMapper;
+import static org.glassfish.jersey.examples.exception.Exceptions.MySubSubException;
+import static org.glassfish.jersey.examples.exception.Exceptions.WebApplicationExceptionMapper;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * ExceptionMappingTest class.
+ *
+ * @author Santiago.PericasGeertsen at oracle.com
+ */
+public class ExceptionMappingTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ // mvn test -Djersey.test.containerFactory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+ // mvn test -Djersey.test.containerFactory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+ return new ResourceConfig(
+ ExceptionResource.class,
+ MyResponseFilter.class,
+ MyExceptionMapper.class,
+ MySubExceptionMapper.class,
+ MySubSubException.class,
+ WebApplicationExceptionMapper.class);
+ }
+
+ /**
+ * Ensure we can access resource with response filter installed.
+ */
+ @Test
+ public void testPingAndFilter() {
+ WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).build());
+ Response r = t.request("text/plain").get();
+ assertEquals(200, r.getStatus());
+ assertTrue(r.readEntity(String.class).contains(MyResponseFilter.class.getSimpleName()));
+ }
+
+ /**
+ * No mapper should be used if WebApplicationException already contains a
+ * Response with a non-empty entity.
+ */
+ @Test
+ public void testWebApplicationExceptionWithEntity() {
+ WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("webapplication_entity").build());
+ Response r = t.request("text/plain").post(Entity.text("Code:200"));
+ assertEquals(200, r.getStatus());
+ final String entity = r.readEntity(String.class);
+ assertTrue(entity.contains("Code:200"));
+ assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+ }
+
+ /**
+ * No mapper should be used if WebApplicationException already contains a
+ * Response with a non-empty entity. Same as last test but using 400 code.
+ */
+ @Test
+ public void testWebApplicationExceptionWithEntity400() {
+ WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("webapplication_entity").build());
+ Response r = t.request("text/plain").post(Entity.text("Code:400"));
+ assertEquals(400, r.getStatus());
+ final String entity = r.readEntity(String.class);
+ assertTrue(entity.contains("Code:400"));
+ assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+ }
+
+ /**
+ * WebApplicationExceptionMapper should be used if WebApplicationException contains
+ * empty entity.
+ */
+ @Test
+ public void testWebApplicationExceptionUsingMapper() {
+ WebTarget t = client()
+ .target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("webapplication_noentity").build());
+ Response r = t.request("text/plain").post(Entity.text("Code:200"));
+ assertEquals(200, r.getStatus());
+ String entity = r.readEntity(String.class);
+ assertTrue(entity.contains("Code:200"));
+ assertTrue(entity.contains(WebApplicationExceptionMapper.class.getSimpleName()));
+ assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+ }
+
+ /**
+ * MyExceptionMapper should be used if MyException is thrown.
+ */
+ @Test
+ public void testMyException() {
+ WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("my").build());
+ Response r = t.request("text/plain").post(Entity.text("Code:200"));
+ assertEquals(200, r.getStatus());
+ String entity = r.readEntity(String.class);
+ assertTrue(entity.contains("Code:200"));
+ assertTrue(entity.contains(MyExceptionMapper.class.getSimpleName()));
+ assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+ }
+
+ /**
+ * MySubExceptionMapper should be used if MySubException is thrown.
+ */
+ @Test
+ public void testMySubException() {
+ WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("mysub").build());
+ Response r = t.request("text/plain").post(Entity.text("Code:200"));
+ assertEquals(200, r.getStatus());
+ String entity = r.readEntity(String.class);
+ assertTrue(entity.contains("Code:200"));
+ assertTrue(entity.contains(MySubExceptionMapper.class.getSimpleName()));
+ assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+ }
+
+ /**
+ * MySubExceptionMapper should be used if MySubSubException is thrown, given that
+ * there is no mapper for MySubSubException and MySubException is the nearest
+ * super type.
+ */
+ @Test
+ public void testMySubSubException() {
+ WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("mysub").build());
+ Response r = t.request("text/plain").post(Entity.text("Code:200"));
+ assertEquals(200, r.getStatus());
+ String entity = r.readEntity(String.class);
+ assertTrue(entity.contains("Code:200"));
+ assertTrue(entity.contains(MySubExceptionMapper.class.getSimpleName()));
+ assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+ }
+}
diff --git a/examples/extended-wadl-webapp/README.MD b/examples/extended-wadl-webapp/README.MD
new file mode 100644
index 0000000..d7a59d1
--- /dev/null
+++ b/examples/extended-wadl-webapp/README.MD
@@ -0,0 +1,63 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Extended WADL WebApp Example
+============================
+
+This example demonstrates how to get an extended WADL generated by
+jersey.
+
+Contents
+--------
+
+The description of what's done here you'll find in the [jersey 1
+wiki](https://wikis.oracle.com/display/Jersey/HowToConfigureExtendedWADL).
+
+The difference in configuration against jersey 1.x is in property
+configuring the custom WadlGeneratorConfig. Instead of property key
+'com.sun.jersey.config.property.WadlGeneratorConfig' use the property
+key 'jersey.config.server.wadl.generatorConfig'. Also check pom.xml of
+this sample to see other configuration differences.
+
+Just as a pointer at which files you might have a look:
+
+- pom.xml
+- src/main/webapp/WEB-INF/web.xml
+- src/main/resources/\*
+- src/main/xsd/schema.xsd
+- org.glassfish.jersey.examples.extendedwadl.SampleWadlGeneratorConfig
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Description
+------------------------------- | ---------------- | -------------- | ---------------------------------------------------------------------------------------------------------------
+**_/items_** | ItemsResource | POST | Creates a new item and allocates a new unique URI for it, the new URI is returned back in the Location header
+**_/items/{id}_** | ItemResource | GET | Returns an item with the id={id}
+**_/items/{id}/value/{val}_** | ItemResource | PUT | Updates the value of the item with the id={id} with the new value {val}
+
+Running the Example
+-------------------
+
+If you are working with Jersey GlassFish update center module installed
+into your existing GlassFish instance, you will need to follow
+instructions at [the module README file](../../README.html) in order to
+deploy the example.
+
+Otherwise, you can run the example using embedded GlassFish as follows:
+
+You can run the example using Grizzly as follows:
+
+> mvn clean package exec:java
+
+Get the generated wadl via curl:
+
+> curl http://localhost:8080/extended-wadl-webapp/application.wadl
+
+Or from a web browser, visit:
+
+> http://localhost:8080/extended-wadl-webapp/application.wadl
diff --git a/examples/extended-wadl-webapp/pom.xml b/examples/extended-wadl-webapp/pom.xml
new file mode 100644
index 0000000..4c14f2e
--- /dev/null
+++ b/examples/extended-wadl-webapp/pom.xml
@@ -0,0 +1,282 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>extended-wadl-webapp</artifactId>
+ <name>jersey-examples-extended-wadl-webapp</name>
+ <packaging>war</packaging>
+
+ <description>Extended WADL example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ </dependency>
+ <dependency>
+ <!-- Extended Wadl Config: Dependency to jersey-wadl-doclet is needed here to keep the correct build order. -->
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-wadl-doclet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-servlet</artifactId>
+ </dependency>
+
+ <!-- Pax-Exam and JUnit dependencies -->
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-junit4</artifactId>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-container-forked</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-junit-extender-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.tinybundles</groupId>
+ <artifactId>tinybundles</artifactId>
+ <scope>test</scope>
+ <version>2.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-link-mvn</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- OSGi runtime - Felix -->
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+
+ <!-- logging -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <version>1.6.4</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.codehaus.jettison</groupId>
+ <artifactId>jettison</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>aopalliance-repackaged</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.javassist</groupId>
+ <artifactId>javassist</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Extended Wadl Config: this plugin generates the jaxb beans from xsd schemas -->
+ <plugin>
+ <groupId>org.jvnet.jaxb2.maven2</groupId>
+ <artifactId>maven-jaxb2-plugin</artifactId>
+ <version>0.8.0</version>
+ <executions>
+ <execution>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>generate</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <generatePackage>org.glassfish.jersey.examples.extendedwadl</generatePackage>
+ <schemaDirectory>src/main/xsd</schemaDirectory>
+ <!--<includeSchemas>-->
+ <!--<includeSchema>**/*.xsd</includeSchema>-->
+ <!--</includeSchemas>-->
+ <extension>true</extension>
+ <strict>false</strict>
+ <verbose>true</verbose>
+ </configuration>
+ </plugin>
+
+ <!-- Extended Wadl Config: javadoc plugin generates resourcedoc.xml during the compilation using doclet.
+ Generated file is then used during runtime to attach javadoc information to wadl. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>javadoc</goal>
+ </goals>
+ <phase>process-classes</phase>
+ </execution>
+ </executions>
+ <configuration>
+ <encoding>UTF-8</encoding>
+ <verbose>false</verbose>
+ <show>public</show>
+ <subpackages>org.glassfish.jersey.examples.extendedwadl</subpackages>
+ <doclet>org.glassfish.jersey.wadl.doclet.ResourceDoclet</doclet>
+ <docletPath>${path.separator}${project.build.outputDirectory}</docletPath>
+ <docletArtifacts>
+ <!-- jersey doclet generator -->
+ <docletArtifact>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-wadl-doclet</artifactId>
+ <version>${project.version}</version>
+ </docletArtifact>
+ <!--
+ Also specify jersey and xerces as doclet artifacts as the ResourceDoclet
+ uses classes provided by them to generate the resourcedoc.
+ -->
+ <docletArtifact>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ </docletArtifact>
+ <docletArtifact>
+ <groupId>xerces</groupId>
+ <artifactId>xercesImpl</artifactId>
+ <version>${xerces.version}</version>
+ </docletArtifact>
+ </docletArtifacts>
+ <!-- the following option is required as a work around for
+ version 2.5 of the javadoc plugin which will be used
+ by a maven version > 2.0.9 -->
+ <useStandardDocletOptions>false</useStandardDocletOptions>
+ <additionalparam>-output ${project.build.outputDirectory}/resourcedoc.xml</additionalparam>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkMode>always</forkMode>
+ <enableAssertions>false</enableAssertions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>maven-paxexam-plugin</artifactId>
+ </plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.extendedwadl.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>transform-gf-pom</id>
+ <phase>package</phase>
+ <goals>
+ <goal>transform</goal>
+ </goals>
+ <configuration>
+ <transformationSets>
+ <transformationSet>
+ <dir>${project.build.directory}/gf-pom-file</dir>
+ <includes>
+ <include>pom.xml</include>
+ </includes>
+ <stylesheet>src/main/xslt/gf.xsl</stylesheet>
+ <outputDir>${project.build.directory}/gf-pom-file</outputDir>
+ </transformationSet>
+ </transformationSets>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>sonar</id>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <!-- disable JaCoCo listener because it's not working with <forkMode>always</fork> (<reuseForks>false</reuseForks> respectively)
+ https://jira.sonarsource.com/browse/SONARJAVA-728 (https://github.com/SonarSource/sonar-java/pull/324) -->
+ <properties combine.self="override" />
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
+ </profiles>
+</project>
diff --git a/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/App.java b/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/App.java
new file mode 100644
index 0000000..b552d3a
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/App.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.extendedwadl;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.examples.extendedwadl.resources.MyApplication;
+import org.glassfish.jersey.grizzly2.servlet.GrizzlyWebContainerFactory;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.glassfish.jersey.servlet.ServletProperties;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+
+/**
+ * Runner for the Jersey extended-wadl-webapp sample.
+ *
+ * @author Miroslav Fuksa
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/extended-wadl-webapp");
+ public static final String ROOT_PATH = "/application.wadl";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("Extended WADL web application example");
+
+ Map<String, String> initParams = new HashMap<>();
+
+ initParams.put(
+ ServletProperties.JAXRS_APPLICATION_CLASS,
+ MyApplication.class.getName());
+
+ initParams.put(ServerProperties.WADL_GENERATOR_CONFIG, "org.glassfish.jersey.examples.extendedwadl"
+ + ".SampleWadlGeneratorConfig");
+
+ final HttpServer server = GrizzlyWebContainerFactory.create(BASE_URI, ServletContainer.class, initParams);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+
+ System.out.println(String.format("Application started.%nTry out %s%s%nStop the application using CTRL+C",
+ BASE_URI, ROOT_PATH));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+}
diff --git a/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/SampleWadlGeneratorConfig.java b/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/SampleWadlGeneratorConfig.java
new file mode 100644
index 0000000..dd70bde
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/SampleWadlGeneratorConfig.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.extendedwadl;
+
+import java.util.List;
+
+import org.glassfish.jersey.server.wadl.config.WadlGeneratorConfig;
+import org.glassfish.jersey.server.wadl.config.WadlGeneratorDescription;
+import org.glassfish.jersey.server.wadl.internal.generators.WadlGeneratorApplicationDoc;
+import org.glassfish.jersey.server.wadl.internal.generators.WadlGeneratorGrammarsSupport;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.WadlGeneratorResourceDocSupport;
+
+/**
+ * This subclass of {@link WadlGeneratorConfig} defines/configures {@link org.glassfish.jersey.server.wadl.WadlGenerator}s
+ * to be used for generating WADL.
+ *
+ * @author Martin Grotzke (martin.grotzke@freiheit.com)
+ */
+public class SampleWadlGeneratorConfig extends WadlGeneratorConfig {
+
+ @Override
+ public List<WadlGeneratorDescription> configure() {
+ return generator(WadlGeneratorApplicationDoc.class)
+ .prop("applicationDocsStream", "application-doc.xml")
+ .generator(WadlGeneratorGrammarsSupport.class)
+ .prop("grammarsStream", "application-grammars.xml")
+ .prop("overrideGrammars", true)
+ .generator(WadlGeneratorResourceDocSupport.class)
+ .prop("resourceDocStream", "resourcedoc.xml")
+ .descriptions();
+ }
+
+}
diff --git a/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/resources/ItemResource.java b/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/resources/ItemResource.java
new file mode 100644
index 0000000..97ae2ee
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/resources/ItemResource.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.extendedwadl.resources;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.glassfish.jersey.examples.extendedwadl.Item;
+
+import org.codehaus.jettison.json.JSONArray;
+
+/**
+ * This resource is used to manage a single item.
+ *
+ * @author Martin Grotzke (martin.grotzke@freiheit.com)
+ */
+public class ItemResource {
+
+ private final Item _item;
+
+ public ItemResource(Item item) {
+ _item = item;
+ }
+
+ /**
+ * Typically returns the item if it exists. Please be aware that this method is extremely
+ * expensive, so we can't guarantee that getting items is all the time possible.
+ *
+ * @response.representation.200.qname {http://www.example.com}item
+ * @response.representation.200.mediaType application/xml
+ * @response.representation.200.doc This is the representation returned by default
+ * (if we have an even number of millis since 1970...:)
+ * @response.representation.200.example {@link org.glassfish.jersey.examples.extendedwadl.util.Examples#SAMPLE_ITEM}
+ *
+ * @response.representation.503.mediaType text/plain
+ * @response.representation.503.example You'll get some explanation why this service is not available
+ *
+ * @return the requested item if this service is available, otherwise a 503.
+ */
+ @GET
+ @Produces({"application/xml", "text/plain"})
+ public Response getItem() {
+ if (System.currentTimeMillis() % 2 == 0) {
+ return Response.status(Status.SERVICE_UNAVAILABLE)
+ .entity("Sorry, but right now we can't process this request,"
+ + " try again an odd number of milliseconds later, please :)")
+ .type(MediaType.TEXT_PLAIN).build();
+ }
+ return Response.ok(_item).build();
+ }
+
+ /**
+ * Tries hard to return the item if it exists. If "Try-Hard" header is set to "true", the method is guaranteed to always
+ * complete successfully if the item exists.
+ *
+ * @request.param {@name Try-Hard}
+ * {@style header}
+ * {@type {http://www.w3.org/2001/XMLSchema}string}
+ * {@doc If set to "true", the call will always succeed provided the item exists.}
+ *
+ * @response.representation.200.qname {http://www.example.com}item
+ * @response.representation.200.mediaType application/xml
+ * @response.representation.200.doc This is the representation returned by default
+ * (if we have an even number of millis since 1970...:)
+ * @response.representation.200.example {@link org.glassfish.jersey.examples.extendedwadl.util.Examples#SAMPLE_ITEM}
+ *
+ * @response.representation.503.mediaType text/plain
+ * @response.representation.503.example You'll get some explanation why this service is not available.
+ *
+ * @return the requested item if it exists and the "Try-Hard" header is set to "true", otherwise a 503.
+ */
+ // Method added to reproduce OWLS-24243 issue.
+ @GET
+ @Produces({"application/xml", "text/plain"})
+ @Path("try-hard")
+ public Response getItem(@HeaderParam("Try-Hard") boolean tryHard) {
+ if (!tryHard) {
+ return Response.status(Status.SERVICE_UNAVAILABLE)
+ .entity("Sorry, but right now we can't process this request,"
+ + " try again and set the \"Try-Hard\" header to \"true\"")
+ .type(MediaType.TEXT_PLAIN)
+ .build();
+ }
+ return Response.ok(_item).build();
+ }
+
+
+ /**
+ * Returns the item if existing.
+ *
+ * @response.representation.200.mediaType application/json
+ * @response.representation.200.example ["myValue"]
+ *
+ * @return the requested item.
+ */
+ @GET
+ @Produces({"application/json"})
+ public JSONArray getItemAsJSON() {
+ final JSONArray result = new JSONArray();
+ result.put(_item.getValue());
+ return result;
+ }
+
+ /**
+ * Update the value property of the current item.
+ *
+ * @param value the new value to set
+ */
+ @Path("value/{value}")
+ @PUT
+ @Consumes({"application/xml"})
+ public void updateItemValue(@PathParam("value") String value) {
+ _item.setValue(value);
+ }
+
+}
diff --git a/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/resources/ItemsResource.java b/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/resources/ItemsResource.java
new file mode 100644
index 0000000..b1b7f51
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/resources/ItemsResource.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.extendedwadl.resources;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.examples.extendedwadl.Item;
+
+/**
+ * This is the root resource for managing items.
+ *
+ * @author Martin Grotzke (martin.grotzke@freiheit.com)
+ */
+@Singleton
+@Path("items")
+public class ItemsResource {
+
+ @Context
+ UriInfo _uriInfo;
+ private final AtomicInteger _sequence;
+ private final Map<Integer, Item> _repository;
+
+ public ItemsResource() {
+ _sequence = new AtomicInteger();
+ _repository = new HashMap<>();
+ }
+
+ /**
+ * Get an item resource for an item from the list of managed items based on the assigned id extracted from the path parameter.
+ *
+ * @param id The ID of the item to retrieve.
+ * @return respective items resource.
+ *
+ */
+ @Path("{id}")
+ public ItemResource getItem(@PathParam("id") final Integer id) {
+ final Item item = _repository.get(id);
+ if (item == null) {
+ throw new NotFoundException(Response.status(Response.Status.NOT_FOUND).entity("Item with id " + id + " does not "
+ + "exist!").build());
+ }
+
+ return new ItemResource(item);
+ }
+
+ /**
+ * Add a new item to the list of managed items. The item will get assigned an id,
+ * the resource where the item is available will be returned in the location header.
+ *
+ * @param item The item to create.
+ *
+ * @request.representation.qname {http://www.example.com}item
+ * @request.representation.mediaType application/xml
+ * @request.representation.example {@link org.glassfish.jersey.examples.extendedwadl.util.Examples#SAMPLE_ITEM}
+ *
+ * @response.param {@name Location}
+ * {@style header}
+ * {@type {http://www.w3.org/2001/XMLSchema}anyURI}
+ * {@doc The URI where the created item is accessable.}
+ *
+ * @return The response with the status code and the location header.
+ *
+ */
+ @POST
+ @Consumes({"application/xml"})
+ public Response createItem(Item item) {
+ final Integer id = _sequence.incrementAndGet();
+ _repository.put(id, item);
+ return Response.created(
+ _uriInfo.getAbsolutePathBuilder().clone().path(id.toString()).build())
+ .build();
+ }
+
+}
diff --git a/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/resources/MyApplication.java b/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/resources/MyApplication.java
new file mode 100644
index 0000000..93a7289
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/resources/MyApplication.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.extendedwadl.resources;
+
+import org.glassfish.jersey.examples.extendedwadl.SampleWadlGeneratorConfig;
+import org.glassfish.jersey.examples.extendedwadl.util.Examples;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * @author Jonathan Benoit
+ */
+public class MyApplication extends ResourceConfig {
+ public MyApplication() {
+ super(ItemResource.class, ItemsResource.class, Examples.class, SampleWadlGeneratorConfig.class);
+ }
+}
diff --git a/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/util/Examples.java b/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/util/Examples.java
new file mode 100644
index 0000000..702ee0b
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/main/java/org/glassfish/jersey/examples/extendedwadl/util/Examples.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.extendedwadl.util;
+
+import org.glassfish.jersey.examples.extendedwadl.Item;
+
+/**
+ * This class provides example representations that can be used for wadl docs.<br>
+ * Created on: Jul 20, 2008<br>
+ *
+ * @author Martin Grotzke (martin.grotzke@freiheit.com)
+ */
+public class Examples {
+
+ public static final Item SAMPLE_ITEM = new Item();
+
+ static {
+ SAMPLE_ITEM.setValue("foo");
+ }
+
+}
diff --git a/examples/extended-wadl-webapp/src/main/resources/application-doc.xml b/examples/extended-wadl-webapp/src/main/resources/application-doc.xml
new file mode 100644
index 0000000..9b1ecc8
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/main/resources/application-doc.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<applicationDocs targetNamespace="http://wadl.dev.java.net/2009/02">
+
+ <doc xml:lang="en" title="The doc for your API">
+ This is a paragraph that is added to the start of the generated application.wadl
+ </doc>
+
+ <doc xml:lang="de" title="Die Dokumentation fuer Ihre API">
+ Dies in ein Abschnitt, der dem Beginn der generierte application.wadl hinzugefügt wird - in deutscher Sprache.
+ </doc>
+</applicationDocs>
diff --git a/examples/extended-wadl-webapp/src/main/resources/application-grammars.xml b/examples/extended-wadl-webapp/src/main/resources/application-grammars.xml
new file mode 100644
index 0000000..6827aa0
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/main/resources/application-grammars.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<grammars xmlns="http://wadl.dev.java.net/2009/02"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:xi="http://www.w3.org/1999/XML/xinclude">
+ <include href="schema.xsd" />
+</grammars>
diff --git a/examples/extended-wadl-webapp/src/main/webapp/WEB-INF/glassfish-web.xml b/examples/extended-wadl-webapp/src/main/webapp/WEB-INF/glassfish-web.xml
new file mode 100644
index 0000000..8ddb1e4
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/main/webapp/WEB-INF/glassfish-web.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
+<glassfish-web-app error-url="">
+ <context-root>/extended-wadl-webapp</context-root>
+ <class-loader delegate="true" />
+ <jsp-config>
+ <property name="classdebuginfo" value="true">
+ <description>Enable debug info compilation in the generated servlet class</description>
+ </property>
+ <property name="keepgenerated" value="true">
+ <description>Keep a copy of the generated servlet class' java code.</description>
+ </property>
+ <property name="mappedfile" value="true">
+ <description>Maintain a one-to-one correspondence between static content and the generated servlet class' java code</description>
+ </property>
+ </jsp-config>
+</glassfish-web-app>
diff --git a/examples/extended-wadl-webapp/src/main/webapp/WEB-INF/web.xml b/examples/extended-wadl-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..ccb2ede
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+ <servlet>
+ <servlet-name>org.glassfish.jersey.examples.extendedwadl.resources.MyApplication</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.glassfish.jersey.examples.extendedwadl.resources.MyApplication</param-value>
+ </init-param>
+ <init-param>
+ <param-name>jersey.config.server.wadl.generatorConfig</param-name>
+ <param-value>org.glassfish.jersey.examples.extendedwadl.SampleWadlGeneratorConfig</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>org.glassfish.jersey.examples.extendedwadl.resources.MyApplication</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+</web-app>
diff --git a/examples/extended-wadl-webapp/src/main/xsd/schema.xsd b/examples/extended-wadl-webapp/src/main/xsd/schema.xsd
new file mode 100644
index 0000000..b08d2bc
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/main/xsd/schema.xsd
@@ -0,0 +1,35 @@
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<schema
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:example="http://www.example.com"
+ targetNamespace="http://www.example.com"
+ >
+
+ <element name="items" type="example:items"/>
+
+ <complexType name="items">
+ <sequence>
+ <element name="item" type="string" minOccurs="0" maxOccurs="unbounded"/>
+ </sequence>
+ </complexType>
+
+ <element name="item">
+ <complexType>
+ <simpleContent>
+ <extension base="string"></extension>
+ </simpleContent>
+ </complexType>
+ </element>
+
+</schema>
diff --git a/examples/extended-wadl-webapp/src/main/xslt/gf.xsl b/examples/extended-wadl-webapp/src/main/xslt/gf.xsl
new file mode 100644
index 0000000..39ad615
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/main/xslt/gf.xsl
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:pom="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ version="1.0">
+
+ <xsl:output method="xml" indent="yes" />
+
+ <xsl:template match="/">
+ <xsl:apply-templates />
+ </xsl:template>
+
+ <xsl:template
+ match="pom:plugins/pom:plugin[pom:artifactId='maven-surefire-plugin']/pom:configuration">
+ <xsl:copy>
+ <xsl:apply-templates />
+ <excludes>
+ <exclude>**/ExtendedWadlWebappOsgiTest.java</exclude>
+ </excludes>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="comment()">
+ <xsl:comment>
+ <xsl:value-of select="." />
+ </xsl:comment>
+ </xsl:template>
+
+ <xsl:template match="*">
+ <xsl:copy>
+ <xsl:apply-templates />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/examples/extended-wadl-webapp/src/test/java/org/glassfish/jersey/examples/extendedwadl/ExtendedWadlWebappOsgiTest.java b/examples/extended-wadl-webapp/src/test/java/org/glassfish/jersey/examples/extendedwadl/ExtendedWadlWebappOsgiTest.java
new file mode 100644
index 0000000..2853bf8
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/test/java/org/glassfish/jersey/examples/extendedwadl/ExtendedWadlWebappOsgiTest.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.extendedwadl;
+
+import java.io.ByteArrayInputStream;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import javax.inject.Inject;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+import org.glassfish.jersey.examples.extendedwadl.resources.ItemResource;
+import org.glassfish.jersey.examples.extendedwadl.resources.ItemsResource;
+import org.glassfish.jersey.examples.extendedwadl.resources.MyApplication;
+import org.glassfish.jersey.examples.extendedwadl.util.Examples;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.internal.util.SimpleNamespaceResolver;
+import org.glassfish.jersey.message.internal.MediaTypes;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.wadl.internal.WadlUtils;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.w3c.dom.Document;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+import static org.ops4j.pax.exam.CoreOptions.systemPackage;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+import static org.ops4j.pax.tinybundles.core.TinyBundles.bundle;
+
+/**
+ * @author Naresh
+ * @author Miroslav Fuksa
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@RunWith(PaxExam.class)
+public class ExtendedWadlWebappOsgiTest {
+
+ @Inject
+ BundleContext bundleContext;
+
+ private static final Logger LOGGER = Logger.getLogger(ExtendedWadlWebappOsgiTest.class.getName());
+
+ // we want to re-use the port number as set for Jersey test container to avoid CT port number clashes
+ private static final String testContainerPort = System.getProperty(TestProperties.CONTAINER_PORT);
+ private static final int testPort = testContainerPort == null
+ ? TestProperties.DEFAULT_CONTAINER_PORT : Integer.parseInt(testContainerPort);
+
+ private static final URI baseUri = UriBuilder
+ .fromUri("http://localhost")
+ .port(testPort)
+ .path("extended-wadl-webapp").build();
+
+ @Configuration
+ public static Option[] configuration() {
+ List<Option> options = Arrays.asList(options(
+ // systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("FINEST"),
+ systemProperty("org.osgi.framework.system.packages.extra").value("javax.annotation"),
+
+ // javax.annotation must go first!
+ mavenBundle().groupId("javax.annotation").artifactId("javax.annotation-api").versionAsInProject(),
+
+ junitBundles(),
+
+ mavenBundle("org.ops4j.pax.url", "pax-url-mvn"),
+
+ // HK2
+ mavenBundle().groupId("org.glassfish.hk2").artifactId("hk2-api").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2").artifactId("osgi-resource-locator").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2").artifactId("hk2-locator").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2").artifactId("hk2-utils").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2.external").artifactId("javax.inject").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2.external").artifactId("aopalliance-repackaged").versionAsInProject(),
+ mavenBundle().groupId("org.javassist").artifactId("javassist").versionAsInProject(),
+
+ // JAX-RS API
+ mavenBundle().groupId("javax.ws.rs").artifactId("javax.ws.rs-api").versionAsInProject(),
+
+ // Jersey bundles
+ mavenBundle().groupId("org.glassfish.jersey.core").artifactId("jersey-common").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.jersey.media").artifactId("jersey-media-jaxb").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.jersey.core").artifactId("jersey-server").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.jersey.core").artifactId("jersey-client").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.jersey.inject").artifactId("jersey-hk2").versionAsInProject(),
+
+ // jettison
+ mavenBundle().groupId("org.codehaus.jettison").artifactId("jettison").versionAsInProject(),
+
+ // validation
+ mavenBundle().groupId("javax.validation").artifactId("validation-api").versionAsInProject(),
+
+ // Grizzly
+ systemPackage("sun.misc"), // required by grizzly-framework
+ mavenBundle().groupId("org.glassfish.grizzly").artifactId("grizzly-framework").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.grizzly").artifactId("grizzly-http").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.grizzly").artifactId("grizzly-http-server").versionAsInProject(),
+
+ // Jersey Grizzly
+ mavenBundle().groupId("org.glassfish.jersey.containers").artifactId("jersey-container-grizzly2-http")
+ .versionAsInProject(),
+
+ // tinybundles + required dependencies
+ mavenBundle().groupId("org.ops4j.pax.tinybundles").artifactId("tinybundles").versionAsInProject(),
+ mavenBundle().groupId("biz.aQute.bnd").artifactId("bndlib").versionAsInProject(),
+
+ // create ad-hoc bundle
+ provision(
+ bundle()
+ .add(MyApplication.class)
+ .add(ItemResource.class)
+ .add(ItemsResource.class)
+ .add(Examples.class)
+ .add(SampleWadlGeneratorConfig.class)
+ .add(Item.class)
+ .add(Items.class)
+ .add(ObjectFactory.class)
+ .add("application-doc.xml", ClassLoader.getSystemResourceAsStream("application-doc.xml"))
+ .add("application-grammars.xml",
+ ClassLoader.getSystemResourceAsStream("application-grammars.xml"))
+ .add("resourcedoc.xml", ClassLoader.getSystemResourceAsStream("resourcedoc.xml"))
+ .set("Export-Package",
+ MyApplication.class.getPackage().getName() + "," + SampleWadlGeneratorConfig.class
+ .getPackage().getName())
+ .set("DynamicImport-Package", "*")
+ .set("Bundle-SymbolicName", "webapp").build())
+ ));
+ final String localRepository = AccessController.doPrivileged(PropertiesHelper.getSystemProperty("localRepository"));
+ if (localRepository != null) {
+ options = new ArrayList<>(options);
+ options.add(systemProperty("org.ops4j.pax.url.mvn.localRepository").value(localRepository));
+ }
+ return options.toArray(new Option[options.size()]);
+ }
+
+ private ResourceConfig createResourceConfig() {
+ final ResourceConfig resourceConfig = new ResourceConfig(new MyApplication().getClasses());
+ resourceConfig.property(ServerProperties.WADL_GENERATOR_CONFIG, SampleWadlGeneratorConfig.class.getName());
+
+ return resourceConfig;
+ }
+
+ /**
+ * Test checks that the WADL generated using the WadlGenerator api doesn't
+ * contain the expected text.
+ *
+ * @throws java.lang.Exception in case of a test error.
+ */
+ @Test
+ public void testExtendedWadl() throws Exception {
+
+ // TODO - temporary workaround
+ // This is a workaround related to issue JERSEY-2093; grizzly (1.9.5) needs to have the correct context
+ // class loader set
+ ClassLoader myClassLoader = this.getClass().getClassLoader();
+
+ for (Bundle bundle : bundleContext.getBundles()) {
+ if ("webapp".equals(bundle.getSymbolicName())) {
+ myClassLoader = bundle.loadClass("org.glassfish.jersey.examples.extendedwadl.resources.MyApplication")
+ .getClassLoader();
+ break;
+ }
+ }
+
+ Thread.currentThread().setContextClassLoader(myClassLoader);
+ // END of workaround - the entire block can be deleted after grizzly is updated to recent version
+
+ // List all the OSGi bundles
+ StringBuilder sb = new StringBuilder();
+ sb.append("-- Bundle list -- \n");
+ for (Bundle b : bundleContext.getBundles()) {
+ sb.append(String.format("%1$5s", "[" + b.getBundleId() + "]")).append(" ")
+ .append(String.format("%1$-70s", b.getSymbolicName())).append(" | ")
+ .append(String.format("%1$-20s", b.getVersion())).append(" |");
+ try {
+ b.start();
+ sb.append(" STARTED | ");
+ } catch (BundleException e) {
+ sb.append(" *FAILED* | ").append(e.getMessage());
+ }
+ sb.append(b.getLocation()).append("\n");
+ }
+ sb.append("-- \n\n");
+ LOGGER.fine(sb.toString());
+
+ final ResourceConfig resourceConfig = createResourceConfig();
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(baseUri, resourceConfig);
+ final Client client = ClientBuilder.newClient();
+ final Response response = client.target(baseUri).path("application.wadl").request(MediaTypes.WADL_TYPE).buildGet()
+ .invoke();
+
+ String wadl = response.readEntity(String.class);
+ LOGGER.info("RESULT = " + wadl);
+
+ assertTrue("Generated wadl is of null length", !wadl.isEmpty());
+ assertTrue("Generated wadl doesn't contain the expected text",
+ wadl.contains("This is a paragraph"));
+
+ assertFalse(wadl.contains("application.wadl/xsd0.xsd"));
+
+ server.shutdownNow();
+ }
+
+ @Test
+ public void testWadlOptionsMethod() throws Exception {
+ // TODO - temporary workaround
+ // This is a workaround related to issue JERSEY-2093; grizzly (1.9.5) needs to have the correct context
+ // class loader set
+ ClassLoader myClassLoader = this.getClass().getClassLoader();
+ for (Bundle bundle : bundleContext.getBundles()) {
+ if ("webapp".equals(bundle.getSymbolicName())) {
+ myClassLoader = bundle.loadClass("org.glassfish.jersey.examples.extendedwadl.resources.MyApplication")
+ .getClassLoader();
+ break;
+ }
+ }
+
+ Thread.currentThread().setContextClassLoader(myClassLoader);
+ // END of workaround; The entire block can be removed after grizzly is migrated to more recent version
+
+ final ResourceConfig resourceConfig = createResourceConfig();
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(baseUri, resourceConfig);
+ final Client client = ClientBuilder.newClient();
+
+ String wadl = client.target(baseUri).path("items").queryParam(WadlUtils.DETAILED_WADL_QUERY_PARAM, "true")
+ .request(MediaTypes.WADL_TYPE).options(String.class);
+
+ assertTrue("Generated wadl is of null length", !wadl.isEmpty());
+ assertTrue("Generated wadl doesn't contain the expected text",
+ wadl.contains("This is a paragraph"));
+
+ checkWadl(wadl, baseUri);
+
+ server.shutdownNow();
+ }
+
+ private void checkWadl(String wadl, URI baseUri) throws Exception {
+ DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
+ bf.setNamespaceAware(true);
+ bf.setValidating(false);
+ DocumentBuilder b = bf.newDocumentBuilder();
+ Document document = b.parse(new ByteArrayInputStream(wadl.getBytes(Charset.forName("UTF-8"))));
+ XPath xp = XPathFactory.newInstance().newXPath();
+ xp.setNamespaceContext(new SimpleNamespaceResolver("wadl", "http://wadl.dev.java.net/2009/02"));
+ String val = (String) xp.evaluate("/wadl:application/wadl:resources/@base", document, XPathConstants.STRING);
+ assertEquals(baseUri.toString(), val.endsWith("/") ? val.substring(0, val.length() - 1) : val);
+ val = (String) xp.evaluate("count(//wadl:resource)", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of resource elements.", val, "4");
+ val = (String) xp.evaluate("count(//wadl:resource[@path='items'])", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of resource elements with 'items' path.", "1", val);
+ val = (String) xp.evaluate("count(//wadl:resource[@path='{id}'])", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of resource elements with '{id}' path.", "1", val);
+ val = (String) xp.evaluate("count(//wadl:resource[@path='try-hard'])", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of resource elements with 'try-hard' path.", "1", val);
+ val = (String) xp.evaluate("count(//wadl:resource[@path='value/{value}'])", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of resource elements with 'value/{value}' path.", "1", val);
+
+ val = (String) xp.evaluate("count(//wadl:resource[@path='{id}']/wadl:method)", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of methods in resource element with '{id}' path.", "2", val);
+ val = (String) xp.evaluate("count(//wadl:resource[@path='{id}']/wadl:method[@id='getItem']"
+ + "/wadl:doc[contains(., 'Typically returns the item if it exists.')])",
+ document, XPathConstants.STRING);
+ assertEquals("Unexpected documentation of getItem resource method at '{id}' path", "1", val);
+
+ val = (String) xp.evaluate("count(//wadl:resource[@path='try-hard']/wadl:method)", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of methods in resource element with 'try-hard' path.", "1", val);
+ val = (String) xp.evaluate("count(//wadl:resource[@path='try-hard']/wadl:method[@id='getItem']"
+ + "/wadl:doc[contains(., 'Tries hard to return the item if it exists.')])",
+ document, XPathConstants.STRING);
+ assertEquals("Unexpected documentation of getItem resource method at 'try-hard' path", "1", val);
+
+ val = (String) xp.evaluate("count(//wadl:resource[@path='items']/wadl:method)", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of methods in resource element with 'items' path.", "4", val);
+ val = (String) xp.evaluate("count(//wadl:resource[@path='value/{value}']/wadl:method)", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of methods in resource element with 'value/{value}' path.", "1", val);
+
+ val = (String) xp.evaluate("count(//wadl:application/wadl:doc)", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of doc elements in application element.", "3", val);
+ }
+}
diff --git a/examples/extended-wadl-webapp/src/test/java/org/glassfish/jersey/examples/extendedwadl/ExtendedWadlWebappTest.java b/examples/extended-wadl-webapp/src/test/java/org/glassfish/jersey/examples/extendedwadl/ExtendedWadlWebappTest.java
new file mode 100644
index 0000000..e2050b1
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/test/java/org/glassfish/jersey/examples/extendedwadl/ExtendedWadlWebappTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.extendedwadl;
+
+import java.io.ByteArrayInputStream;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.util.logging.Logger;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Response;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+import org.glassfish.jersey.examples.extendedwadl.resources.MyApplication;
+import org.glassfish.jersey.internal.util.SimpleNamespaceResolver;
+import org.glassfish.jersey.message.internal.MediaTypes;
+import org.glassfish.jersey.process.Inflector;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.model.Resource;
+import org.glassfish.jersey.server.wadl.internal.WadlUtils;
+import org.glassfish.jersey.test.DeploymentContext;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import org.w3c.dom.Document;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * WADL extension example tests.
+ *
+ * @author Naresh
+ * @author Miroslav Fuksa
+ */
+public class ExtendedWadlWebappTest extends JerseyTest {
+
+ private static final Logger LOGGER = Logger.getLogger(ExtendedWadlWebappTest.class.getName());
+
+ @Override
+ protected DeploymentContext configureDeployment() {
+ final ResourceConfig resourceConfig = new ResourceConfig(new MyApplication().getClasses());
+ resourceConfig.property(ServerProperties.WADL_GENERATOR_CONFIG, "org.glassfish.jersey.examples.extendedwadl"
+ + ".SampleWadlGeneratorConfig");
+
+ final Resource.Builder resourceBuilder = Resource.builder();
+ resourceBuilder.name("resource-programmatic").path("programmatic").addMethod("GET")
+
+ .handledBy(new ProgrammaticResource());
+ resourceConfig.registerResources(resourceBuilder.build());
+ return DeploymentContext.builder(resourceConfig).contextPath("extended-wadl-webapp").build();
+ }
+
+ /**
+ * Test checks that the WADL generated using the WadlGenerator api doesn't
+ * contain the expected text.
+ *
+ * @throws java.lang.Exception in case of test error.
+ */
+ @Test
+ public void testExtendedWadl() throws Exception {
+ String wadl = target().path("application.wadl")
+ .queryParam(WadlUtils.DETAILED_WADL_QUERY_PARAM, "true").request(MediaTypes.WADL_TYPE).get(String.class);
+
+ LOGGER.fine(wadl);
+ assertTrue("Generated wadl is of null length", !wadl.isEmpty());
+ assertTrue("Generated wadl doesn't contain the expected text",
+ wadl.contains("This is a paragraph"));
+
+ assertFalse(wadl.contains("application.wadl/xsd0.xsd"));
+ }
+
+ @Test
+ public void testWadlOptionsMethod() throws Exception {
+ String wadl = target().path("items")
+ .queryParam(WadlUtils.DETAILED_WADL_QUERY_PARAM, "true").request(MediaTypes.WADL_TYPE).options(String.class);
+
+ LOGGER.fine(wadl);
+ assertTrue("Generated wadl is of null length", !wadl.isEmpty());
+ assertTrue("Generated wadl doesn't contain the expected text",
+ wadl.contains("This is a paragraph"));
+ checkWadl(wadl, getBaseUri());
+ }
+
+ /**
+ * Programmatic resource class javadoc.
+ */
+ private static class ProgrammaticResource implements Inflector<ContainerRequestContext, Response> {
+ @Override
+ public Response apply(ContainerRequestContext data) {
+ return Response.ok("programmatic").build();
+ }
+ }
+
+ private void checkWadl(String wadl, URI baseUri) throws Exception {
+ DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
+ bf.setNamespaceAware(true);
+ bf.setValidating(false);
+// if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) {
+// bf.setXIncludeAware(false);
+// }
+ DocumentBuilder b = bf.newDocumentBuilder();
+ Document document = b.parse(new ByteArrayInputStream(wadl.getBytes(Charset.forName("UTF-8"))));
+ XPath xp = XPathFactory.newInstance().newXPath();
+ xp.setNamespaceContext(new SimpleNamespaceResolver("wadl", "http://wadl.dev.java.net/2009/02"));
+ String val = (String) xp.evaluate("/wadl:application/wadl:resources/@base", document, XPathConstants.STRING);
+ assertEquals(baseUri.toString(), val.endsWith("/") ? val.substring(0, val.length() - 1) : val);
+ val = (String) xp.evaluate("count(//wadl:resource)", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of resource elements.", val, "4");
+ val = (String) xp.evaluate("count(//wadl:resource[@path='items'])", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of resource elements with 'items' path.", "1", val);
+ val = (String) xp.evaluate("count(//wadl:resource[@path='{id}'])", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of resource elements with '{id}' path.", "1", val);
+ val = (String) xp.evaluate("count(//wadl:resource[@path='try-hard'])", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of resource elements with 'try-hard' path.", "1", val);
+ val = (String) xp.evaluate("count(//wadl:resource[@path='value/{value}'])", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of resource elements with 'value/{value}' path.", "1", val);
+
+ val = (String) xp.evaluate("count(//wadl:resource[@path='{id}']/wadl:method)", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of methods in resource element with '{id}' path.", "2", val);
+ val = (String) xp.evaluate("count(//wadl:resource[@path='{id}']/wadl:method[@id='getItem']"
+ + "/wadl:doc[contains(., 'Typically returns the item if it exists.')])",
+ document, XPathConstants.STRING);
+ assertEquals("Unexpected documentation of getItem resource method at '{id}' path", "1", val);
+
+ val = (String) xp.evaluate("count(//wadl:resource[@path='try-hard']/wadl:method)", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of methods in resource element with 'try-hard' path.", "1", val);
+ val = (String) xp.evaluate("count(//wadl:resource[@path='try-hard']/wadl:method[@id='getItem']"
+ + "/wadl:doc[contains(., 'Tries hard to return the item if it exists.')])",
+ document, XPathConstants.STRING);
+ assertEquals("Unexpected documentation of getItem resource method at 'try-hard' path", "1", val);
+
+ val = (String) xp.evaluate("count(//wadl:resource[@path='items']/wadl:method)", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of methods in resource element with 'items' path.", "4", val);
+ val = (String) xp.evaluate("count(//wadl:resource[@path='value/{value}']/wadl:method)", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of methods in resource element with 'value/{value}' path.", "1", val);
+
+ val = (String) xp.evaluate("count(//wadl:application/wadl:doc)", document, XPathConstants.STRING);
+ assertEquals("Unexpected number of doc elements in application element.", "3", val);
+ }
+}
diff --git a/examples/extended-wadl-webapp/src/test/resources/log4j.properties b/examples/extended-wadl-webapp/src/test/resources/log4j.properties
new file mode 100644
index 0000000..01fd9e5
--- /dev/null
+++ b/examples/extended-wadl-webapp/src/test/resources/log4j.properties
@@ -0,0 +1,14 @@
+#
+# Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Distribution License v. 1.0, which is available at
+# http://www.eclipse.org/org/documents/edl-v10.php.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+log4j.rootCategory=DEBUG, stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout.ConversionPattern=[%30.30c{1}] - %m%n
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
diff --git a/examples/freemarker-webapp/README.MD b/examples/freemarker-webapp/README.MD
new file mode 100644
index 0000000..11bfa2b
--- /dev/null
+++ b/examples/freemarker-webapp/README.MD
@@ -0,0 +1,52 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Freemaker example
+=================
+
+This example demonstrates how to use Freemarker templating engine to
+produce web pages via Jersey viewable support.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+--------------------------- | -------------------- | --------------
+**_/hello_** | FreemarkerResource | GET
+**_/autoTemplate_** | FreemarkerResource | GET
+**_/helloWithoutSuffix_** | FreemarkerResource | GET
+
+See FreemarkerResource for details on how to use jersey-freemaker-webapp
+module for generating web pages.
+
+If you want to use freemarker in your project, don't forget to set
+**"jersey.config.freemarker.TemplateBasePath"** init param, which will
+be pointing to base directory where your templates are stored
+(FreemarkerTest class does that).
+
+Running the Example
+-------------------
+
+Run the tests of the example as follows:
+
+> mvn clean test
+
+This deploys this example using [Grizzly](http://grizzly.java.net/) and
+executes the Jersey Test Framework using the unit test framework.
+
+You can see test classes at `src/main/test` subdirectory for detailed
+information how to consume the service using Client API.
+
+Run the example as follows:
+
+> mvn clean package jetty:run
+
+This deploys current example using Jetty. You can access the application
+at <http://localhost:8080/freemarker-webapp/hello>
\ No newline at end of file
diff --git a/examples/freemarker-webapp/pom.xml b/examples/freemarker-webapp/pom.xml
new file mode 100644
index 0000000..97c6b2b
--- /dev/null
+++ b/examples/freemarker-webapp/pom.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>freemarker-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-freemarker-webapp</name>
+
+ <description>Jersey Freemarker example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-mvc-freemarker</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Run the application using "mvn jetty:run" -->
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>maven-jetty-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/freemarker-webapp/src/main/java/org/glassfish/jersey/examples/freemarker/MyApplication.java b/examples/freemarker-webapp/src/main/java/org/glassfish/jersey/examples/freemarker/MyApplication.java
new file mode 100644
index 0000000..ae318cc
--- /dev/null
+++ b/examples/freemarker-webapp/src/main/java/org/glassfish/jersey/examples/freemarker/MyApplication.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.freemarker;
+
+import org.glassfish.jersey.examples.freemarker.resources.FreemarkerResource;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.mvc.freemarker.FreemarkerMvcFeature;
+
+/**
+ * Freemarker application configuration.
+ *
+ * @author Michal Gajdos
+ */
+public class MyApplication extends ResourceConfig {
+
+ public MyApplication() {
+ super(FreemarkerResource.class);
+
+ register(LoggingFeature.class);
+ register(FreemarkerMvcFeature.class);
+ }
+}
diff --git a/examples/freemarker-webapp/src/main/java/org/glassfish/jersey/examples/freemarker/resources/FreemarkerResource.java b/examples/freemarker-webapp/src/main/java/org/glassfish/jersey/examples/freemarker/resources/FreemarkerResource.java
new file mode 100644
index 0000000..61b71e3
--- /dev/null
+++ b/examples/freemarker-webapp/src/main/java/org/glassfish/jersey/examples/freemarker/resources/FreemarkerResource.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.freemarker.resources;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.server.mvc.Viewable;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Path("/")
+public class FreemarkerResource {
+
+ @GET
+ @Path("hello")
+ @Produces(MediaType.TEXT_HTML)
+ public Viewable getHello() {
+ final Map<String, Object> map = new HashMap<String, Object>();
+ map.put("user", "Pavel");
+ final List<String> list = new ArrayList<String>();
+ list.add("item1");
+ list.add("item2");
+ list.add("item3");
+ map.put("items", list);
+
+ return new Viewable("/hello.ftl", map);
+ }
+
+ @GET
+ @Path("hello-default-model")
+ @Produces(MediaType.TEXT_HTML)
+ public Viewable getHelloWithDefaultModel() {
+ return new Viewable("/hello-default-model.ftl", "Pavel");
+ }
+
+ @GET
+ @Path("autoTemplate")
+ @Produces(MediaType.TEXT_HTML)
+ public Viewable getAutoTemplate() {
+ final Map<String, String> map = new HashMap<String, String>();
+ map.put("user", "Pavel");
+
+ // template name is derived from resource class name
+ return new Viewable("/org/glassfish/jersey/examples/freemarker/resources/FreemarkerResource.ftl",
+ map);
+ }
+
+ @GET
+ @Path("helloWithoutSuffix")
+ @Produces(MediaType.TEXT_HTML)
+ public Viewable getHelloWithoutSuffix() {
+ final Map<String, Object> map = new HashMap<String, Object>();
+ map.put("user", "Pavel");
+ final List<String> list = new ArrayList<String>();
+ list.add("item1");
+ list.add("item2");
+ list.add("item3");
+ map.put("items", list);
+
+ return new Viewable("/hello", map);
+ }
+}
diff --git a/examples/freemarker-webapp/src/main/resources/freemarker/hello-default-model.ftl b/examples/freemarker-webapp/src/main/resources/freemarker/hello-default-model.ftl
new file mode 100644
index 0000000..8e3c203
--- /dev/null
+++ b/examples/freemarker-webapp/src/main/resources/freemarker/hello-default-model.ftl
@@ -0,0 +1,20 @@
+<#--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+<html>
+ <head>
+ <title>Welcome!</title>
+ </head>
+
+ <body>
+ <h1>Welcome ${model}!</h1>
+ </body>
+</html>
diff --git a/examples/freemarker-webapp/src/main/resources/freemarker/hello.ftl b/examples/freemarker-webapp/src/main/resources/freemarker/hello.ftl
new file mode 100644
index 0000000..137bfd7
--- /dev/null
+++ b/examples/freemarker-webapp/src/main/resources/freemarker/hello.ftl
@@ -0,0 +1,25 @@
+<#--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+<html>
+ <head>
+ <title>Welcome!</title>
+ </head>
+
+ <body>
+ <h1>Welcome ${user}!</h1>
+ <p>items:<br />
+ <#list items as item>
+ ${item}<br />
+ </#list>
+ </p>
+ </body>
+</html>
diff --git a/examples/freemarker-webapp/src/main/resources/freemarker/org/glassfish/jersey/examples/freemarker/resources/FreemarkerResource.ftl b/examples/freemarker-webapp/src/main/resources/freemarker/org/glassfish/jersey/examples/freemarker/resources/FreemarkerResource.ftl
new file mode 100644
index 0000000..e71ad7b
--- /dev/null
+++ b/examples/freemarker-webapp/src/main/resources/freemarker/org/glassfish/jersey/examples/freemarker/resources/FreemarkerResource.ftl
@@ -0,0 +1,20 @@
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+<html>
+ <head>
+ <title>Welcome - automatically chosen template</title>
+ </head>
+
+ <body>
+ <h1>Welcome ${user}!</h1>
+ </body>
+</html>
diff --git a/examples/freemarker-webapp/src/main/webapp/WEB-INF/glassfish-web.xml b/examples/freemarker-webapp/src/main/webapp/WEB-INF/glassfish-web.xml
new file mode 100644
index 0000000..3965fcb
--- /dev/null
+++ b/examples/freemarker-webapp/src/main/webapp/WEB-INF/glassfish-web.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
+<glassfish-web-app error-url="">
+ <context-root>/freemarker-webapp</context-root>
+ <class-loader delegate="true" />
+ <jsp-config>
+ <property name="classdebuginfo" value="true">
+ <description>Enable debug info compilation in the generated servlet class</description>
+ </property>
+ <property name="keepgenerated" value="true">
+ <description>Keep a copy of the generated servlet class' java code.</description>
+ </property>
+ <property name="mappedfile" value="true">
+ <description>Maintain a one-to-one correspondence between static content and the generated servlet class' java code</description>
+ </property>
+ </jsp-config>
+</glassfish-web-app>
diff --git a/examples/freemarker-webapp/src/main/webapp/WEB-INF/web.xml b/examples/freemarker-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..940b11a
--- /dev/null
+++ b/examples/freemarker-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="2.5"
+ xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+
+ <servlet>
+ <servlet-name>org.glassfish.jersey.examples.freemarker.MyApplication</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.glassfish.jersey.examples.freemarker.MyApplication</param-value>
+ </init-param>
+ <init-param>
+ <param-name>jersey.config.server.mvc.templateBasePath.freemarker</param-name>
+ <param-value>freemarker</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>org.glassfish.jersey.examples.freemarker.MyApplication</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+</web-app>
diff --git a/examples/freemarker-webapp/src/test/java/org/glassfish/jersey/examples/freemarker/FreemarkerTest.java b/examples/freemarker-webapp/src/test/java/org/glassfish/jersey/examples/freemarker/FreemarkerTest.java
new file mode 100644
index 0000000..0701654
--- /dev/null
+++ b/examples/freemarker-webapp/src/test/java/org/glassfish/jersey/examples/freemarker/FreemarkerTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.freemarker;
+
+import java.net.URI;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.mvc.freemarker.FreemarkerMvcFeature;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class FreemarkerTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ enable(TestProperties.DUMP_ENTITY);
+
+ return new MyApplication().property(FreemarkerMvcFeature.TEMPLATE_BASE_PATH, "freemarker");
+ }
+
+ @Override
+ protected URI getBaseUri() {
+ return UriBuilder.fromUri(super.getBaseUri()).path("freemarker-webapp").build();
+ }
+
+ @Test
+ public void testHello() {
+ assertTrue(target().path("hello").request().get(String.class).contains("Pavel"));
+ }
+
+ @Test
+ public void testHellowithDefaultModel() {
+ assertTrue(target().path("hello-default-model").request().get(String.class).contains("Pavel"));
+ }
+
+ @Test
+ public void testAutoTemplate() {
+ assertTrue(target().path("autoTemplate").request().get(String.class).contains("Pavel"));
+ }
+
+ @Test
+ public void testAutoTemplateWithoutSuffix() {
+ assertTrue(target().path("helloWithoutSuffix").request().get(String.class).contains("Pavel"));
+ }
+
+}
diff --git a/examples/groovy/README.MD b/examples/groovy/README.MD
new file mode 100644
index 0000000..68e5c16
--- /dev/null
+++ b/examples/groovy/README.MD
@@ -0,0 +1,41 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Groovy Example
+==============
+
+This example demonstrates how to develop RESTful hello world web service
+using Groovy and the embedded Grizzly server.
+
+Contents
+--------
+
+The example consists of just one Jersey resource:
+
+`org.glassfish.jersey.examples.groovy.GroovyResource`
+
+hat produces a textual response to an HTTP GET.
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+----------- | ---------------- | --------------
+**_/groovy_** | GroovyResource | GET
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
+
+From a web browser, visit:
+
+- <http://localhost:9998/groovy>
diff --git a/examples/groovy/pom.xml b/examples/groovy/pom.xml
new file mode 100644
index 0000000..2ce8088
--- /dev/null
+++ b/examples/groovy/pom.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+ <artifactId>groovy</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-groovy</name>
+ <description>Groovy Jersey</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy-all</artifactId>
+ <version>2.4.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ </dependencies>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>groovy.ui.GroovyMain</mainClass>
+ <arguments>
+ <argument>${pom.basedir}/src/main/script/NewGroovyScript.groovy</argument>
+ </arguments>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <version>1.6</version>
+ <executions>
+ <execution>
+ <id>1</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>addSources</goal>
+ <goal>addTestSources</goal>
+ <goal>generateStubs</goal>
+ <goal>compile</goal>
+ <goal>generateTestStubs</goal>
+ <goal>compileTests</goal>
+ <goal>removeStubs</goal>
+ <goal>removeTestStubs</goal>
+ <goal>groovydoc</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>2</id>
+ <phase>generate-resources</phase>
+ <configuration>
+ <target>
+ <jar destfile="${project.build.directory}/${project.artifactId}-javadoc.jar">
+ <zipfileset dir="${project.build.directory}/gapidocs" />
+ </jar>
+ </target>
+ </configuration>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <version>3.0.0</version>
+ <executions>
+ <execution>
+ <id>3</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/${project.artifactId}-javadoc.jar</file>
+ <type>jar</type>
+ <classifier>javadoc</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/examples/groovy/src/main/groovy/org/glassfish/jersey/examples/groovy/GroovyResource.groovy b/examples/groovy/src/main/groovy/org/glassfish/jersey/examples/groovy/GroovyResource.groovy
new file mode 100644
index 0000000..8f67e15
--- /dev/null
+++ b/examples/groovy/src/main/groovy/org/glassfish/jersey/examples/groovy/GroovyResource.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.groovy
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * Example Groovy JAX-RS resource.
+ */
+@Path("groovy")
+class GroovyResource {
+ @Produces(["text/plain"]) @GET def show() {
+ "groovy"
+ }
+}
diff --git a/examples/groovy/src/main/script/NewGroovyScript.groovy b/examples/groovy/src/main/script/NewGroovyScript.groovy
new file mode 100644
index 0000000..c3aae52
--- /dev/null
+++ b/examples/groovy/src/main/script/NewGroovyScript.groovy
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.groovy
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory
+import org.glassfish.jersey.server.ResourceConfig
+
+import javax.ws.rs.core.UriBuilder
+
+/*
+ * Groovy script to start the example app
+ */
+baseUri = UriBuilder.fromUri("http://localhost/").port(9998).build()
+server = GrizzlyHttpServerFactory.createHttpServer(baseUri, new ResourceConfig(GroovyResource.class))
+
+System.out.println(String.format("Jersey app started with WADL available at " + "%sapplication.wadl\n"
+ + "Try out %sgroovy\nHit enter to stop it...", baseUri, baseUri));
+
+System.in.read()
+server.shutdown()
diff --git a/examples/groovy/src/test/groovy/org/glassfish/jersey/examples/groovy/GroovyResourceTest.groovy b/examples/groovy/src/test/groovy/org/glassfish/jersey/examples/groovy/GroovyResourceTest.groovy
new file mode 100644
index 0000000..11db4f6
--- /dev/null
+++ b/examples/groovy/src/test/groovy/org/glassfish/jersey/examples/groovy/GroovyResourceTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.groovy
+
+import org.glassfish.jersey.server.ResourceConfig
+import org.glassfish.jersey.test.JerseyTest
+import org.glassfish.jersey.test.TestProperties
+import org.junit.Test
+
+import javax.ws.rs.core.Response
+import static org.junit.Assert.assertEquals
+
+/**
+ * Test the availability of the {@link GroovyResource}.
+ */
+class GroovyResourceTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ return new ResourceConfig(GroovyResource.class)
+ }
+
+ @Test
+ public void testGroovyResource() {
+ final Response response = target("groovy").request().get();
+ assertEquals("groovy", response.readEntity(String.class));
+ }
+}
diff --git a/examples/helloworld-benchmark/README.MD b/examples/helloworld-benchmark/README.MD
new file mode 100644
index 0000000..7aa30ca
--- /dev/null
+++ b/examples/helloworld-benchmark/README.MD
@@ -0,0 +1,85 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Hello World JMH Example
+=======================
+
+### *How to write JAX-RS Micro-Benchmarks*
+
+This example demonstrates how to write simple micro-benchmarks, based on
+JMH, for JAX-RS application. This particular example shows how to obtain
+throughput of a simple 'Hello World' application.
+
+The application consists of one resource, `HelloWorldResource` with
+three resource methods (one method per HTTP GET, POST and PUT methods).
+To make it more interesting the resource also acts like sub-resource
+locator which allows us to measure the difference between processing of
+normal resource methods and sub-resource locators.
+
+This application is solely focused on showing how to write benchmarks
+and it doesn't start any server that would allow one to send request to
+the `HelloWorldResource`.
+
+Main class that illustrates writing benchmarks for JAX-RS applications
+is `HelloWorldBenchmark`. Measurements are not based on any container
+such as Servlet or Grizzly container. Requests are directly invoked on
+`ApplicationHandler` class from Jersey Server module to eliminate any
+network influences. Still, all JAX-RS features, like filters and
+interceptors, are taken into consideration when the benchmark is
+executed.
+
+It's also possible to run the benchmark on older versions of Jersey,
+e.g. 2.15, which had known performance issues with sub-resource
+locators. The recommended approach is to run the benchmarks without any
+changes at first and then modify the `pom.xml` to switch version of
+Jersey to 2.15 and run the benchmarks again to see the (big) difference.
+Look for `jersey-bom` artifact in `pom.xml` to see how the switch should
+be done.
+
+Sample Results
+--------------
+
+The execution takes around 2 minutes and you can see partial results
+(for each combination of parameters) of every iteration. At the end JMH
+gives you whole summary. For example, Jersey 2.16 gives the following
+results:
+
+ # Run complete. Total time: 00:01:41
+
+ Benchmark (method) (path) Mode Cnt Score Error Units
+ HelloWorldBenchmark.measure GET helloworld thrpt 8 74343.864 ± 4814.979 ops/s
+ HelloWorldBenchmark.measure GET helloworld/locator thrpt 8 54137.102 ± 8996.766 ops/s
+ HelloWorldBenchmark.measure POST helloworld thrpt 8 45173.853 ± 5349.363 ops/s
+ HelloWorldBenchmark.measure POST helloworld/locator thrpt 8 37144.797 ± 4699.782 ops/s
+ HelloWorldBenchmark.measure PUT helloworld thrpt 8 45945.974 ± 4116.752 ops/s
+ HelloWorldBenchmark.measure PUT helloworld/locator thrpt 8 36345.667 ± 5929.480 ops/s
+
+Running the Example
+-------------------
+
+There are two ways how to run the micro-benchmark. Either via Maven
+`exec:exec`
+
+> mvn clean install exec:exec
+
+or you can build the benchmark JAR at first
+
+> mvn clean install
+
+and then invoking it using `java` command
+
+> java -jar target/benchmark.jar
+
+Resources
+---------
+
+JMH has excellent overview of it's features presented as list of
+different samples. You can find them on [JMH
+Samples](http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/).
+The general overview of JMH is available at their [home
+page](http://openjdk.java.net/projects/code-tools/jmh/).
diff --git a/examples/helloworld-benchmark/pom.xml b/examples/helloworld-benchmark/pom.xml
new file mode 100644
index 0000000..6ce7c4c
--- /dev/null
+++ b/examples/helloworld-benchmark/pom.xml
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>helloworld-benchmark</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-helloworld-benchmark</name>
+
+ <description>Jersey "Hello World" benchmark example.</description>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey</groupId>
+ <artifactId>jersey-bom</artifactId>
+ <type>pom</type>
+ <scope>import</scope>
+
+ <!-- Comment this line to use Jersey 2.15. Un-comment to use the current version. -->
+ <version>${project.version}</version>
+ <!-- Comment this line to use the current version. Un-comment to use Jersey 2.15. -->
+ <!--<version>2.15</version>-->
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-generator-annprocess</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-util</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <executable>java</executable>
+ <arguments>
+ <argument>-classpath</argument>
+ <classpath />
+ <argument>org.openjdk.jmh.Main</argument>
+ </arguments>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <configuration>
+ <minimizeJar>false</minimizeJar>
+ </configuration>
+ <executions>
+ <execution>
+ <id>shade-archive</id>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <finalName>${uberjar.name}</finalName>
+ <transformers>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <mainClass>org.openjdk.jmh.Main</mainClass>
+ </transformer>
+ </transformers>
+ <filters>
+ <filter>
+ <!--
+ Shading signed JARs will fail without this.
+ http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar
+ -->
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>META-INF/*.SF</exclude>
+ <exclude>META-INF/*.DSA</exclude>
+ <exclude>META-INF/*.RSA</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <properties>
+ <uberjar.name>benchmark</uberjar.name>
+ </properties>
+</project>
diff --git a/examples/helloworld-benchmark/src/main/java/org/glassfish/jersey/examples/helloworld/Application.java b/examples/helloworld-benchmark/src/main/java/org/glassfish/jersey/examples/helloworld/Application.java
new file mode 100644
index 0000000..a52b33d
--- /dev/null
+++ b/examples/helloworld-benchmark/src/main/java/org/glassfish/jersey/examples/helloworld/Application.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+
+/**
+ * @author Michal Gajdos
+ */
+public class Application extends ResourceConfig {
+
+ public Application() {
+ register(HelloWorldResource.class);
+
+ // Turn off Monitoring to not affect benchmarks.
+ property(ServerProperties.MONITORING_ENABLED, false);
+ property(ServerProperties.MONITORING_STATISTICS_ENABLED, false);
+ property(ServerProperties.MONITORING_STATISTICS_MBEANS_ENABLED, false);
+ }
+}
diff --git a/examples/helloworld-benchmark/src/main/java/org/glassfish/jersey/examples/helloworld/HelloWorldBenchmark.java b/examples/helloworld-benchmark/src/main/java/org/glassfish/jersey/examples/helloworld/HelloWorldBenchmark.java
new file mode 100644
index 0000000..89c6365
--- /dev/null
+++ b/examples/helloworld-benchmark/src/main/java/org/glassfish/jersey/examples/helloworld/HelloWorldBenchmark.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.test.util.server.ContainerRequestBuilder;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+/**
+ * "Hello world!" Jersey {@link org.glassfish.jersey.server.ApplicationHandler} benchmark.
+ *
+ * @author Michal Gajdos
+ */
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.SECONDS)
+@Warmup(iterations = 8, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 8, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(1)
+@State(Scope.Benchmark)
+public class HelloWorldBenchmark {
+
+ @Param(value = {"helloworld", "helloworld/locator"})
+ private String path;
+
+ @Param(value = {"GET", "POST", "PUT"})
+ private String method;
+
+ private volatile ApplicationHandler handler;
+ private volatile ContainerRequest request;
+
+ @Setup
+ public void start() throws Exception {
+ handler = new ApplicationHandler(new Application());
+ }
+
+ @Setup(Level.Iteration)
+ public void request() {
+ request = ContainerRequestBuilder
+ .from(path, method)
+ .entity("GET".equals(method) ? null : HelloWorldResource.CLICHED_MESSAGE, handler)
+ .build();
+ }
+
+ @TearDown
+ public void shutdown() {
+ }
+
+ @Benchmark
+ public Future<ContainerResponse> measure() throws Exception {
+ return handler.apply(request);
+ }
+
+ public static void main(final String[] args) throws Exception {
+ final Options opt = new OptionsBuilder()
+ // Register our benchmarks.
+ .include(HelloWorldBenchmark.class.getSimpleName())
+ .build();
+
+ new Runner(opt).run();
+ }
+}
diff --git a/examples/helloworld-benchmark/src/main/java/org/glassfish/jersey/examples/helloworld/HelloWorldResource.java b/examples/helloworld-benchmark/src/main/java/org/glassfish/jersey/examples/helloworld/HelloWorldResource.java
new file mode 100644
index 0000000..a57a53d
--- /dev/null
+++ b/examples/helloworld-benchmark/src/main/java/org/glassfish/jersey/examples/helloworld/HelloWorldResource.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * Simple "Hello World" resource with three resource methods and a sub-resource locator (that points again to this class itself).
+ *
+ * @author Michal Gajdos
+ */
+@Path("helloworld")
+@Produces("text/plain")
+public class HelloWorldResource {
+
+ public static final String CLICHED_MESSAGE = "Hello World!";
+
+ @GET
+ public String get() {
+ return CLICHED_MESSAGE;
+ }
+
+ @POST
+ public String post(final String entity) {
+ return entity;
+ }
+
+ @PUT
+ public void put(final String entity) {
+ // NOOP
+ }
+
+ @Path("locator")
+ public Class<?> sub() {
+ return HelloWorldResource.class;
+ }
+}
diff --git a/examples/helloworld-benchmark/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java b/examples/helloworld-benchmark/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java
new file mode 100644
index 0000000..6e7c046
--- /dev/null
+++ b/examples/helloworld-benchmark/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * {@link org.glassfish.jersey.examples.helloworld.HelloWorldResource} tests.
+ *
+ * @author Michal Gajdos
+ */
+public class HelloWorldTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+
+ return new ResourceConfig(HelloWorldResource.class);
+ }
+
+ @Test
+ public void testGet() {
+ final Response response = target().path("helloworld").request("text/plain").get();
+
+ assertThat("Wrong HTTP response code returned.", response.getStatus(), is(200));
+ assertThat("Unexpected response entity.", response.readEntity(String.class), is(HelloWorldResource.CLICHED_MESSAGE));
+ }
+
+ @Test
+ public void testGetLocator() {
+ final Response response = target().path("helloworld").path("locator").request("text/plain").get();
+
+ assertThat("Wrong HTTP response code returned.", response.getStatus(), is(200));
+ assertThat("Unexpected response entity.", response.readEntity(String.class), is(HelloWorldResource.CLICHED_MESSAGE));
+ }
+
+ @Test
+ public void testPost() {
+ final Response response = target().path("helloworld")
+ .request("text/plain")
+ .post(Entity.text(HelloWorldResource.CLICHED_MESSAGE));
+
+ assertThat("Wrong HTTP response code returned.", response.getStatus(), is(200));
+ assertThat("Unexpected response entity.", response.readEntity(String.class), is(HelloWorldResource.CLICHED_MESSAGE));
+ }
+
+ @Test
+ public void testPostLocator() {
+ final Response response = target().path("helloworld").path("locator")
+ .request("text/plain")
+ .post(Entity.text(HelloWorldResource.CLICHED_MESSAGE));
+
+ assertThat("Wrong HTTP response code returned.", response.getStatus(), is(200));
+ assertThat("Unexpected response entity.", response.readEntity(String.class), is(HelloWorldResource.CLICHED_MESSAGE));
+ }
+
+ @Test
+ public void testPut() {
+ final Response response = target().path("helloworld")
+ .request("text/plain")
+ .put(Entity.text(HelloWorldResource.CLICHED_MESSAGE));
+
+ assertThat("Wrong HTTP response code returned.", response.getStatus(), is(204));
+ }
+
+ @Test
+ public void testPutLocator() {
+ final Response response = target().path("helloworld").path("locator")
+ .request("text/plain")
+ .put(Entity.text(HelloWorldResource.CLICHED_MESSAGE));
+
+ assertThat("Wrong HTTP response code returned.", response.getStatus(), is(204));
+ }
+}
diff --git a/examples/helloworld-cdi2-se/README.MD b/examples/helloworld-cdi2-se/README.MD
new file mode 100644
index 0000000..6e4026f
--- /dev/null
+++ b/examples/helloworld-cdi2-se/README.MD
@@ -0,0 +1,34 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Hello World and Counter Example
+===============================
+
+This example demonstrates Hello World and Counter examples with CDI 2 SE implemented by Weld.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Notes
+--------------------------- | ------------------- | ------------ | --------------------------------------------------------
+**_/helloworld/{name}_** | HelloWorldResource | GET | Returns `Hello {name}`
+**_/counter/request_** | CounterResource | GET | Returns always `1` (injected always a new instance of counter)
+**_/counter/application_** | CounterResource | GET | Returns an incremented number (injected always the same counter).
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
+
+- <http://localhost:8080/helloworld>
diff --git a/examples/helloworld-cdi2-se/pom.xml b/examples/helloworld-cdi2-se/pom.xml
new file mode 100644
index 0000000..ba3cfaa
--- /dev/null
+++ b/examples/helloworld-cdi2-se/pom.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>helloworld-cdi2-se</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-helloworld-cdi2-se</name>
+
+ <description>Jersey "Hello world" example with CDI 2 SE.</description>
+
+ <properties>
+ <cdi.api.version>2.0</cdi.api.version>
+ <weld.version>${weld3.version}</weld.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-cdi2-se</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-util</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>java.org.glassfish.jersey.examples.helloworld.cdi2se.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/App.java b/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/App.java
new file mode 100644
index 0000000..d35233f
--- /dev/null
+++ b/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/App.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.cdi2se;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Hello world!
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/base/");
+ public static final String ROOT_HELLO_PATH = "helloworld";
+ public static final String ROOT_COUNTER_PATH = "counter";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Hello World\" Jersey Example App");
+
+ ResourceConfig resourceConfig = new ResourceConfig(HelloWorldResource.class, CounterResource.class);
+ HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, false);
+ Runtime.getRuntime().addShutdownHook(new Thread(server::shutdownNow));
+
+ server.start();
+
+ System.out.println("Application started.\nTry out");
+ System.out.println(String.format("%s%s", BASE_URI, ROOT_HELLO_PATH));
+ System.out.println(String.format("%s%s%s", BASE_URI, ROOT_COUNTER_PATH, "/request"));
+ System.out.println(String.format("%s%s%s", BASE_URI, ROOT_COUNTER_PATH, "/application"));
+ System.out.println("Stop the application using CTRL+C");
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+}
diff --git a/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/ApplicationScopedCounter.java b/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/ApplicationScopedCounter.java
new file mode 100644
index 0000000..119aa08
--- /dev/null
+++ b/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/ApplicationScopedCounter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.cdi2se;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.enterprise.context.ApplicationScoped;
+
+/**
+ * Application-scoped counter.
+ */
+@ApplicationScoped
+public class ApplicationScopedCounter {
+
+ private final AtomicInteger counter = new AtomicInteger();
+
+ public int getNumber() {
+ return counter.incrementAndGet();
+ }
+}
diff --git a/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/CounterResource.java b/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/CounterResource.java
new file mode 100644
index 0000000..519da94
--- /dev/null
+++ b/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/CounterResource.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.cdi2se;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import javax.inject.Inject;
+
+/**
+ * Request-scoped resource counter.
+ *
+ * @author Petr Bouda
+ */
+@Path("counter")
+public class CounterResource {
+
+ @Inject
+ private RequestScopedCounter requestScoped;
+
+ @Inject
+ private ApplicationScopedCounter applicationScoped;
+
+ @GET
+ @Path("application")
+ @Produces("text/plain")
+ public int getAppCounter() {
+ return applicationScoped.getNumber();
+ }
+
+ @GET
+ @Path("request")
+ @Produces("text/plain")
+ public int getReqCounter() {
+ return requestScoped.getNumber();
+ }
+}
diff --git a/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/HelloBean.java b/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/HelloBean.java
new file mode 100644
index 0000000..7a06759
--- /dev/null
+++ b/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/HelloBean.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.cdi2se;
+
+import javax.enterprise.context.ApplicationScoped;
+
+/**
+ * Application-scoped service returning "hello" sentence.
+ *
+ * @author Petr Bouda
+ */
+@ApplicationScoped
+public class HelloBean {
+
+ public String hello(String name) {
+ return "Hello " + name;
+ }
+}
diff --git a/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/HelloWorldResource.java b/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/HelloWorldResource.java
new file mode 100644
index 0000000..7799da3
--- /dev/null
+++ b/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/HelloWorldResource.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.cdi2se;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Singleton-scoped resource.
+ *
+ * @author Petr Bouda
+ */
+@Singleton
+@Path("helloworld")
+public class HelloWorldResource {
+
+ @Inject
+ private HelloBean helloBean;
+
+ @GET
+ @Path("{name}")
+ @Produces("text/plain")
+ public String getHello(@PathParam("name") String name) {
+ return helloBean.hello(name);
+ }
+}
diff --git a/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/RequestScopedCounter.java b/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/RequestScopedCounter.java
new file mode 100644
index 0000000..d457878
--- /dev/null
+++ b/examples/helloworld-cdi2-se/src/main/java/org/glassfish/jersey/examples/helloworld/cdi2se/RequestScopedCounter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.cdi2se;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.enterprise.context.RequestScoped;
+
+/**
+ * Request-scoped counter.
+ */
+@RequestScoped
+public class RequestScopedCounter {
+
+ private final AtomicInteger counter = new AtomicInteger();
+
+ public int getNumber() {
+ return counter.incrementAndGet();
+ }
+}
diff --git a/examples/helloworld-cdi2-se/src/main/resources/META-INF/beans.xml b/examples/helloworld-cdi2-se/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000..fc210a0
--- /dev/null
+++ b/examples/helloworld-cdi2-se/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<beans/>
diff --git a/examples/helloworld-cdi2-se/src/test/java/org/glassfish/jersey/examples/helloworld/cdi2se/CounterTest.java b/examples/helloworld-cdi2-se/src/test/java/org/glassfish/jersey/examples/helloworld/cdi2se/CounterTest.java
new file mode 100644
index 0000000..9017419
--- /dev/null
+++ b/examples/helloworld-cdi2-se/src/test/java/org/glassfish/jersey/examples/helloworld/cdi2se/CounterTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.cdi2se;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests CDI counter resource.
+ */
+public class CounterTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ return new ResourceConfig(CounterResource.class);
+ }
+
+ @Test
+ public void testRequestCounter() throws InterruptedException {
+ Integer response1 = target().path(App.ROOT_COUNTER_PATH).path("request").request().get(Integer.class);
+ Integer response2 = target().path(App.ROOT_COUNTER_PATH).path("request").request().get(Integer.class);
+ assertEquals((Integer) 1, response1);
+ assertEquals((Integer) 1, response2);
+ }
+
+ @Test
+ public void testApplicationCounter() throws InterruptedException {
+ Integer response1 = target().path(App.ROOT_COUNTER_PATH).path("application").request().get(Integer.class);
+ Integer response2 = target().path(App.ROOT_COUNTER_PATH).path("application").request().get(Integer.class);
+ assertEquals((Integer) 1, response1);
+ assertEquals((Integer) 2, response2);
+ }
+}
diff --git a/examples/helloworld-cdi2-se/src/test/java/org/glassfish/jersey/examples/helloworld/cdi2se/HelloWorldTest.java b/examples/helloworld-cdi2-se/src/test/java/org/glassfish/jersey/examples/helloworld/cdi2se/HelloWorldTest.java
new file mode 100644
index 0000000..0bba28a
--- /dev/null
+++ b/examples/helloworld-cdi2-se/src/test/java/org/glassfish/jersey/examples/helloworld/cdi2se/HelloWorldTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.cdi2se;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests CDI helloworld resource.
+ */
+public class HelloWorldTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ return new ResourceConfig(HelloWorldResource.class);
+ }
+
+ @Test
+ public void testHello() throws InterruptedException {
+ String response = target().path(App.ROOT_HELLO_PATH).path("James").request().get(String.class);
+ assertEquals("Hello James", response);
+ }
+}
diff --git a/examples/helloworld-netty/README.MD b/examples/helloworld-netty/README.MD
new file mode 100644
index 0000000..dbf5e9c
--- /dev/null
+++ b/examples/helloworld-netty/README.MD
@@ -0,0 +1,33 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Hello World Example
+===================
+
+This example demonstrates Hello World example running on top of Netty container and using Netty connector as a client
+implementation for testing. JAX-RS resource returns the usual text `Hello World!`.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Notes
+-------------------- | ------------------- | ------------ | --------------------------------------------------------
+**_/helloworld_** | HelloWorldResource | GET | Returns `Hello World!`
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Netty](http://netty.io/) container.
+
+- <http://localhost:8080/helloworld>
diff --git a/examples/helloworld-netty/pom.xml b/examples/helloworld-netty/pom.xml
new file mode 100644
index 0000000..e3ec898
--- /dev/null
+++ b/examples/helloworld-netty/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>helloworld-netty</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-helloworld-netty</name>
+
+ <description>Jersey "Hello world" example on Netty container.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-netty-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-util</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.helloworld.netty.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/helloworld-netty/src/main/java/org/glassfish/jersey/examples/helloworld/netty/App.java b/examples/helloworld-netty/src/main/java/org/glassfish/jersey/examples/helloworld/netty/App.java
new file mode 100644
index 0000000..f118831
--- /dev/null
+++ b/examples/helloworld-netty/src/main/java/org/glassfish/jersey/examples/helloworld/netty/App.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.netty;
+
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import io.netty.channel.Channel;
+import org.glassfish.jersey.netty.httpserver.NettyHttpContainerProvider;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * Hello world!
+ */
+public class App {
+
+ static final String ROOT_PATH = "helloworld";
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/");
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Hello World\" Jersey Example App on Netty container.");
+
+ ResourceConfig resourceConfig = new ResourceConfig(HelloWorldResource.class);
+ final Channel server = NettyHttpContainerProvider.createHttp2Server(BASE_URI, resourceConfig, null);
+
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.close();
+ }
+ }));
+
+ System.out.println(String.format("Application started. (HTTP/2 enabled!)\nTry out %s%s\nStop the application using "
+ + "CTRL+C.", BASE_URI, ROOT_PATH));
+ Thread.currentThread().join();
+ } catch (InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+}
diff --git a/examples/helloworld-netty/src/main/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldResource.java b/examples/helloworld-netty/src/main/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldResource.java
new file mode 100644
index 0000000..baf1be7
--- /dev/null
+++ b/examples/helloworld-netty/src/main/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldResource.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.netty;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("helloworld")
+public class HelloWorldResource {
+ public static final String CLICHED_MESSAGE = "Hello World!";
+
+ @GET
+ @Produces("text/plain")
+ public String getHello() {
+ return CLICHED_MESSAGE;
+ }
+
+}
diff --git a/examples/helloworld-netty/src/test/java/org/glassfish/jersey/examples/helloworld/netty/CustomLoggingFilter.java b/examples/helloworld-netty/src/test/java/org/glassfish/jersey/examples/helloworld/netty/CustomLoggingFilter.java
new file mode 100644
index 0000000..396abdd
--- /dev/null
+++ b/examples/helloworld-netty/src/test/java/org/glassfish/jersey/examples/helloworld/netty/CustomLoggingFilter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.netty;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Custom logging filter.
+ *
+ * @author Santiago Pericas-Geertsen (santiago.pericasgeertsen at oracle.com)
+ */
+public class CustomLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter,
+ ClientRequestFilter, ClientResponseFilter {
+
+ private static final Logger LOGGER = Logger.getLogger(CustomLoggingFilter.class.getName());
+
+ static int preFilterCalled = 0;
+ static int postFilterCalled = 0;
+
+ @Override
+ public void filter(ClientRequestContext context) throws IOException {
+ LOGGER.info("CustomLoggingFilter.preFilter called");
+ assertEquals(context.getConfiguration().getProperty("foo"), "bar");
+ preFilterCalled++;
+ }
+
+ @Override
+ public void filter(ClientRequestContext context, ClientResponseContext clientResponseContext) throws IOException {
+ LOGGER.info("CustomLoggingFilter.postFilter called");
+ assertEquals(context.getConfiguration().getProperty("foo"), "bar");
+ postFilterCalled++;
+ }
+
+ @Override
+ public void filter(ContainerRequestContext context) throws IOException {
+ LOGGER.info("CustomLoggingFilter.preFilter called");
+ assertEquals(context.getProperty("foo"), "bar");
+ preFilterCalled++;
+ }
+
+ @Override
+ public void filter(ContainerRequestContext context, ContainerResponseContext containerResponseContext) throws IOException {
+ LOGGER.info("CustomLoggingFilter.postFilter called");
+ assertEquals(context.getProperty("foo"), "bar");
+ postFilterCalled++;
+ }
+}
+
diff --git a/examples/helloworld-netty/src/test/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldTest.java b/examples/helloworld-netty/src/test/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldTest.java
new file mode 100644
index 0000000..cdb82b5
--- /dev/null
+++ b/examples/helloworld-netty/src/test/java/org/glassfish/jersey/examples/helloworld/netty/HelloWorldTest.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.netty;
+
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.netty.connector.NettyConnectorProvider;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.glassfish.jersey.test.netty.NettyTestContainerFactory;
+import org.glassfish.jersey.test.spi.TestContainerException;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+import org.glassfish.jersey.test.util.runner.ConcurrentRunner;
+import org.glassfish.jersey.test.util.runner.RunSeparately;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(ConcurrentRunner.class)
+public class HelloWorldTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ // enable(TestProperties.DUMP_ENTITY);
+ return new ResourceConfig(HelloWorldResource.class);
+ }
+
+ @Override
+ protected void configureClient(ClientConfig clientConfig) {
+ clientConfig.connectorProvider(new NettyConnectorProvider());
+ }
+
+ @Override
+ protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
+ return new NettyTestContainerFactory();
+ }
+
+ @Test
+ @Ignore("not compatible with test framework (doesn't use client())")
+ public void testHelloWorld() throws Exception {
+ URL getUrl = UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).build().toURL();
+ HttpURLConnection connection = (HttpURLConnection) getUrl.openConnection();
+ try {
+ connection.setDoOutput(true);
+ connection.setInstanceFollowRedirects(false);
+ connection.setRequestMethod("GET");
+ connection.setRequestProperty("Content-Type", "text/plain");
+ assertEquals(HttpURLConnection.HTTP_OK, connection.getResponseCode());
+ } finally {
+ connection.disconnect();
+ }
+ }
+
+ @Test
+ public void testConnection() {
+ Response response = target().path(App.ROOT_PATH).request("text/plain").get();
+ assertEquals(200, response.getStatus());
+ }
+
+ @Test
+ public void testClientStringResponse() {
+ String s = target().path(App.ROOT_PATH).request().get(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+ }
+
+ @Test
+ public void testAsyncClientRequests() throws InterruptedException {
+ final int REQUESTS = 10;
+ final CountDownLatch latch = new CountDownLatch(REQUESTS);
+ final long tic = System.currentTimeMillis();
+ for (int i = 0; i < REQUESTS; i++) {
+ final int id = i;
+ target().path(App.ROOT_PATH).request().async().get(new InvocationCallback<Response>() {
+ @Override
+ public void completed(Response response) {
+ try {
+ final String result = response.readEntity(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, result);
+ } finally {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void failed(Throwable error) {
+ error.printStackTrace();
+ latch.countDown();
+ }
+ });
+ }
+ latch.await(10 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS);
+ final long toc = System.currentTimeMillis();
+ Logger.getLogger(HelloWorldTest.class.getName()).info("Executed in: " + (toc - tic));
+ }
+
+ @Test
+ public void testHead() {
+ Response response = target().path(App.ROOT_PATH).request().head();
+ assertEquals(200, response.getStatus());
+ assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType());
+ }
+
+ @Test
+ public void testFooBarOptions() {
+ Response response = target().path(App.ROOT_PATH).request().header("Accept", "foo/bar").options();
+ assertEquals(200, response.getStatus());
+ final String allowHeader = response.getHeaderString("Allow");
+ _checkAllowContent(allowHeader);
+ assertEquals("foo/bar", response.getMediaType().toString());
+ assertEquals(0, response.getLength());
+ }
+
+ @Test
+ public void testTextPlainOptions() {
+ Response response = target().path(App.ROOT_PATH).request().header("Accept", MediaType.TEXT_PLAIN).options();
+ assertEquals(200, response.getStatus());
+ final String allowHeader = response.getHeaderString("Allow");
+ _checkAllowContent(allowHeader);
+ assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType());
+ final String responseBody = response.readEntity(String.class);
+ _checkAllowContent(responseBody);
+ }
+
+ private void _checkAllowContent(final String content) {
+ assertTrue(content.contains("GET"));
+ assertTrue(content.contains("HEAD"));
+ assertTrue(content.contains("OPTIONS"));
+ }
+
+ @Test
+ public void testMissingResourceNotFound() {
+ Response response;
+
+ response = target().path(App.ROOT_PATH + "arbitrary").request().get();
+ assertEquals(404, response.getStatus());
+
+ response = target().path(App.ROOT_PATH).path("arbitrary").request().get();
+ assertEquals(404, response.getStatus());
+ }
+
+ @Test
+ @RunSeparately
+ public void testLoggingFilterClientClass() {
+ Client client = client();
+ client.register(CustomLoggingFilter.class).property("foo", "bar");
+ CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+ String s = target().path(App.ROOT_PATH).request().get(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+ assertEquals(1, CustomLoggingFilter.preFilterCalled);
+ assertEquals(1, CustomLoggingFilter.postFilterCalled);
+ }
+
+ @Test
+ @RunSeparately
+ public void testLoggingFilterClientInstance() {
+ Client client = client();
+ client.register(new CustomLoggingFilter()).property("foo", "bar");
+ CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+ String s = target().path(App.ROOT_PATH).request().get(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+ assertEquals(1, CustomLoggingFilter.preFilterCalled);
+ assertEquals(1, CustomLoggingFilter.postFilterCalled);
+ }
+
+ @Test
+ @RunSeparately
+ public void testLoggingFilterTargetClass() {
+ WebTarget target = target().path(App.ROOT_PATH);
+ target.register(CustomLoggingFilter.class).property("foo", "bar");
+ CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+ String s = target.request().get(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+ assertEquals(1, CustomLoggingFilter.preFilterCalled);
+ assertEquals(1, CustomLoggingFilter.postFilterCalled);
+ }
+
+ @Test
+ @RunSeparately
+ public void testLoggingFilterTargetInstance() {
+ WebTarget target = target().path(App.ROOT_PATH);
+ target.register(new CustomLoggingFilter()).property("foo", "bar");
+ CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+ String s = target.request().get(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+ assertEquals(1, CustomLoggingFilter.preFilterCalled);
+ assertEquals(1, CustomLoggingFilter.postFilterCalled);
+ }
+
+ @Test
+ @RunSeparately
+ public void testConfigurationUpdate() {
+ Client client1 = client();
+ client1.register(CustomLoggingFilter.class).property("foo", "bar");
+
+ Client client = ClientBuilder.newClient(client1.getConfiguration());
+ CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+ String s = target().path(App.ROOT_PATH).request().get(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+ assertEquals(1, CustomLoggingFilter.preFilterCalled);
+ assertEquals(1, CustomLoggingFilter.postFilterCalled);
+ }
+}
diff --git a/examples/helloworld-programmatic/README.MD b/examples/helloworld-programmatic/README.MD
new file mode 100644
index 0000000..4fd210d
--- /dev/null
+++ b/examples/helloworld-programmatic/README.MD
@@ -0,0 +1,35 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Hello World Example
+===================
+
+This example demonstrates how to create programmatically Hello World example.
+JAX-RS resource returns the usual text `Hello World!`.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Notes
+-------------------- | ------------------- | ------------ | --------------------------------------------------------
+**_/helloworld_** | HelloWorldResource | GET | Returns `Hello World!`
+**_/helloworld_** | HelloWorldResource | OPTIONS | Returns HTTP Status 204
+**_/helloworld_** | HelloWorldResource | HEAD | Returns HTTP Status 204
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container. You can access the application at:
+
+- <http://localhost:8080/base/helloworld>
diff --git a/examples/helloworld-programmatic/pom.xml b/examples/helloworld-programmatic/pom.xml
new file mode 100644
index 0000000..5356b50
--- /dev/null
+++ b/examples/helloworld-programmatic/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>helloworld-programmatic</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-helloworld-programmatic</name>
+
+ <description>Jersey programmatic resource API "Hello world" example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.helloworld.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/helloworld-programmatic/src/main/java/org/glassfish/jersey/examples/helloworld/App.java b/examples/helloworld-programmatic/src/main/java/org/glassfish/jersey/examples/helloworld/App.java
new file mode 100644
index 0000000..5969cc5
--- /dev/null
+++ b/examples/helloworld-programmatic/src/main/java/org/glassfish/jersey/examples/helloworld/App.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.process.Inflector;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.model.Resource;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * This is the example entry point, where Jersey application for the example
+ * gets populated and published using the Grizzly 2 HTTP container.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/base/");
+ /**
+ * "Hello World" root resource path.
+ */
+ public static final String ROOT_PATH = "helloworld";
+
+ /**
+ * Main application entry point.
+ *
+ * @param args application arguments.
+ */
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Hello World\" Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, create(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(
+ String.format("Application started.%n"
+ + "Try out %s%s%n"
+ + "Stop the application using CTRL+C",
+ BASE_URI, ROOT_PATH));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ /**
+ * Test assertion indicator that a GET method handler has been called.
+ */
+ public static volatile boolean getMethodCalled = false;
+ /**
+ * Test assertion indicator that a HEAD method handler has been called.
+ */
+ public static volatile boolean headMethodCalled = false;
+
+ /**
+ * Create example application resource configuration.
+ *
+ * @return initialized resource configuration of the example application.
+ */
+ public static ResourceConfig create() {
+ final Resource.Builder resourceBuilder = Resource.builder(ROOT_PATH);
+
+ resourceBuilder.addMethod("GET").handledBy(new Inflector<ContainerRequestContext, Response>() {
+
+ @Override
+ public Response apply(ContainerRequestContext data) {
+ getMethodCalled = true;
+ return Response.ok("Hello World!").build();
+ }
+ });
+
+ Inflector<ContainerRequestContext, Response> noContentResponder = new Inflector<ContainerRequestContext, Response>() {
+
+ @Override
+ public Response apply(ContainerRequestContext data) {
+ headMethodCalled = true;
+ return Response.noContent().build();
+ }
+ };
+ resourceBuilder.addMethod("HEAD").handledBy(noContentResponder);
+ resourceBuilder.addMethod("OPTIONS").handledBy(noContentResponder);
+
+ return new ResourceConfig().registerResources(resourceBuilder.build());
+ }
+}
diff --git a/examples/helloworld-programmatic/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java b/examples/helloworld-programmatic/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java
new file mode 100644
index 0000000..f9596ef
--- /dev/null
+++ b/examples/helloworld-programmatic/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class HelloWorldTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ return App.create();
+ }
+
+ @Test
+ public void testHelloWorld() throws Exception {
+ Client client = ClientBuilder.newClient();
+
+ assertFalse(App.getMethodCalled);
+ Response response = client.target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).build().toString())
+ .request("text/plain").get();
+ assertTrue(App.getMethodCalled);
+ assertEquals(200, response.getStatus());
+ assertTrue(response.hasEntity());
+ assertTrue("Hello World!".equals(response.readEntity(String.class)));
+
+ String s = client.target(getBaseUri()).path("helloworld").request().get(String.class);
+ assertTrue(s.equals("Hello World!"));
+ }
+
+ @Test
+ public void testHelloWorldOtherMethods() throws Exception {
+ Client client = ClientBuilder.newClient();
+ assertFalse(App.headMethodCalled);
+ Response response = client.target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).build().toString())
+ .request("text/plain").head();
+ assertTrue(App.headMethodCalled);
+ assertEquals(204, response.getStatus());
+
+ response = client.target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).build().toString()).request("text/plain")
+ .options();
+ assertEquals(204, response.getStatus());
+ }
+}
diff --git a/examples/helloworld-pure-jax-rs/README.MD b/examples/helloworld-pure-jax-rs/README.MD
new file mode 100644
index 0000000..8129168
--- /dev/null
+++ b/examples/helloworld-pure-jax-rs/README.MD
@@ -0,0 +1,32 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Hello World Example
+===================
+
+This example demonstrates Hello World example. JAX-RS resource returns the usual text `Hello World!`.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Notes
+-------------------- | ------------------- | ------------ | --------------------------------------------------------
+**_/helloworld_** | HelloWorldResource | GET | Returns `Hello World!`
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container. You can access the application at:
+
+- <http://localhost:8080/helloworld>
diff --git a/examples/helloworld-pure-jax-rs/pom.xml b/examples/helloworld-pure-jax-rs/pom.xml
new file mode 100644
index 0000000..7e34ec4
--- /dev/null
+++ b/examples/helloworld-pure-jax-rs/pom.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>helloworld-pure-jax-rs</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-helloworld-pure-jax-rs</name>
+
+ <description>Example using only the standard JAX-RS API's and the lightweight HTTP server bundled in JDK.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-jdk-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.helloworld.jaxrs.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/helloworld-pure-jax-rs/src/main/java/org/glassfish/jersey/examples/helloworld/jaxrs/App.java b/examples/helloworld-pure-jax-rs/src/main/java/org/glassfish/jersey/examples/helloworld/jaxrs/App.java
new file mode 100644
index 0000000..3bb193c
--- /dev/null
+++ b/examples/helloworld-pure-jax-rs/src/main/java/org/glassfish/jersey/examples/helloworld/jaxrs/App.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.jaxrs;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.URI;
+
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.ext.RuntimeDelegate;
+
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * Hello world application using only the standard JAX-RS API and lightweight HTTP server bundled in JDK.
+ *
+ * @author Martin Matula
+ */
+public class App {
+
+ /**
+ * Starts the lightweight HTTP server serving the JAX-RS application.
+ *
+ * @return new instance of the lightweight HTTP server
+ * @throws IOException
+ */
+ static HttpServer startServer() throws IOException {
+ // create a new server listening at port 8080
+ final HttpServer server = HttpServer.create(new InetSocketAddress(getBaseURI().getPort()), 0);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.stop(0);
+ }
+ }));
+
+ // create a handler wrapping the JAX-RS application
+ HttpHandler handler = RuntimeDelegate.getInstance().createEndpoint(new JaxRsApplication(), HttpHandler.class);
+
+ // map JAX-RS handler to the server root
+ server.createContext(getBaseURI().getPath(), handler);
+
+ // start the server
+ server.start();
+
+ return server;
+ }
+
+ public static void main(String[] args) throws IOException, InterruptedException {
+ System.out.println("\"Hello World\" Jersey Example Application");
+
+ startServer();
+
+ System.out.println("Application started.\n"
+ + "Try accessing " + getBaseURI() + "helloworld in the browser.\n"
+ + "Hit enter to stop the application...");
+
+ Thread.currentThread().join();
+ }
+
+ private static int getPort(int defaultPort) {
+ final String port = System.getProperty("jersey.config.test.container.port");
+ if (null != port) {
+ try {
+ return Integer.parseInt(port);
+ } catch (NumberFormatException e) {
+ System.out.println("Value of jersey.config.test.container.port property"
+ + " is not a valid positive integer [" + port + "]."
+ + " Reverting to default [" + defaultPort + "].");
+ }
+ }
+ return defaultPort;
+ }
+
+ /**
+ * Gets base {@link URI}.
+ *
+ * @return base {@link URI}.
+ */
+ public static URI getBaseURI() {
+ return UriBuilder.fromUri("http://localhost/").port(getPort(8080)).build();
+ }
+}
diff --git a/examples/helloworld-pure-jax-rs/src/main/java/org/glassfish/jersey/examples/helloworld/jaxrs/HelloWorldResource.java b/examples/helloworld-pure-jax-rs/src/main/java/org/glassfish/jersey/examples/helloworld/jaxrs/HelloWorldResource.java
new file mode 100644
index 0000000..856f233
--- /dev/null
+++ b/examples/helloworld-pure-jax-rs/src/main/java/org/glassfish/jersey/examples/helloworld/jaxrs/HelloWorldResource.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.jaxrs;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("helloworld")
+public class HelloWorldResource {
+ public static final String CLICHED_MESSAGE = "Hello World!";
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getHello() {
+ return CLICHED_MESSAGE;
+ }
+
+}
diff --git a/examples/helloworld-pure-jax-rs/src/main/java/org/glassfish/jersey/examples/helloworld/jaxrs/JaxRsApplication.java b/examples/helloworld-pure-jax-rs/src/main/java/org/glassfish/jersey/examples/helloworld/jaxrs/JaxRsApplication.java
new file mode 100644
index 0000000..240ea1c
--- /dev/null
+++ b/examples/helloworld-pure-jax-rs/src/main/java/org/glassfish/jersey/examples/helloworld/jaxrs/JaxRsApplication.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.jaxrs;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.core.Application;
+
+/**
+ * JAX-RS Application class for this example.
+ *
+ * @author Martin Matula
+ */
+public class JaxRsApplication extends Application {
+ private final Set<Class<?>> classes;
+
+ public JaxRsApplication() {
+ HashSet<Class<?>> c = new HashSet<Class<?>>();
+ c.add(HelloWorldResource.class);
+ classes = Collections.unmodifiableSet(c);
+ }
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ return classes;
+ }
+}
diff --git a/examples/helloworld-pure-jax-rs/src/test/java/org/glassfish/jersey/examples/helloworld/jaxrs/HelloWorldTest.java b/examples/helloworld-pure-jax-rs/src/test/java/org/glassfish/jersey/examples/helloworld/jaxrs/HelloWorldTest.java
new file mode 100644
index 0000000..2562a4f
--- /dev/null
+++ b/examples/helloworld-pure-jax-rs/src/test/java/org/glassfish/jersey/examples/helloworld/jaxrs/HelloWorldTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.jaxrs;
+
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * Simple test to confirm the server is running and serving our resource.
+ *
+ * @author Martin Matula
+ */
+public class HelloWorldTest {
+ @Test
+ public void testHelloWorld() throws Exception {
+ HttpServer server = App.startServer();
+
+ WebTarget target = ClientBuilder.newClient().target(App.getBaseURI() + "helloworld");
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, target.request(MediaType.TEXT_PLAIN).get(String.class));
+
+ server.stop(0);
+ }
+}
diff --git a/examples/helloworld-spring-annotations/README.MD b/examples/helloworld-spring-annotations/README.MD
new file mode 100644
index 0000000..359a595
--- /dev/null
+++ b/examples/helloworld-spring-annotations/README.MD
@@ -0,0 +1,37 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Jersey Spring Integration Spring Helloworld WebApp Example
+==========================================================
+
+This example demonstrates how to create simple Jersey application that
+utilises Spring 3 DI features based on Spring annotations, packaged into WAR file.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+------------------------------------------ | ------------------------- | --------------
+**_/spring-resource_** | SpringRequestResource | GET
+**_/spring-resource/goodbye_** | SpringRequestResource | GET
+**_/spring-resource/norwegian-goodbye_** | SpringRequestResource | GET
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
+
+- <http://localhost:8080/base/spring-resource>
+- <http://localhost:8080/base/spring-resource/goodbye>
+- <http://localhost:8080/base/spring-resource/norwegian-goodbye>
\ No newline at end of file
diff --git a/examples/helloworld-spring-annotations/pom.xml b/examples/helloworld-spring-annotations/pom.xml
new file mode 100644
index 0000000..555e33e
--- /dev/null
+++ b/examples/helloworld-spring-annotations/pom.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>project</artifactId>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>helloworld-spring-annotations</artifactId>
+ <description>Spring 4 Integration Jersey Example</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-spring4</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ <version>${spring4.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-inmemory</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.hello.spring.annotations.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>delayed-strategy-skip-test</id>
+ <activation>
+ <property>
+ <name>org.glassfish.jersey.injection.manager.strategy</name>
+ <value>delayed</value>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skipTests>true</skipTests>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/App.java b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/App.java
new file mode 100644
index 0000000..3556034
--- /dev/null
+++ b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/App.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.hello.spring.annotations;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+/**
+ * @author Petr Bouda
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/base/");
+
+ public static final String ROOT_PATH = "base";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Hello World\" Jersey-Spring Example App");
+
+ final JerseyConfig resourceConfig = new JerseyConfig();
+ resourceConfig.property("contextConfig", new AnnotationConfigApplicationContext(SpringAnnotationConfig.class));
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.\nTry out %s%s\nStop the application using CTRL+C",
+ BASE_URI, ROOT_PATH));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+}
diff --git a/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/EnglishGoodbyeService.java b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/EnglishGoodbyeService.java
new file mode 100644
index 0000000..117174a
--- /dev/null
+++ b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/EnglishGoodbyeService.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.hello.spring.annotations;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class EnglishGoodbyeService implements GoodbyeService {
+
+ @Override
+ public String goodbye(final String who) {
+ return String.format("goodbye, %s!", who);
+ }
+}
diff --git a/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/GoodbyeService.java b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/GoodbyeService.java
new file mode 100644
index 0000000..9657d8a
--- /dev/null
+++ b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/GoodbyeService.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.hello.spring.annotations;
+
+public interface GoodbyeService {
+
+ String goodbye(String who);
+}
diff --git a/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/GreetingService.java b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/GreetingService.java
new file mode 100644
index 0000000..213f107
--- /dev/null
+++ b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/GreetingService.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.hello.spring.annotations;
+
+import org.springframework.stereotype.Service;
+
+/**
+ * Simple greeting service
+ *
+ * @author Geoffroy Warin (http://geowarin.github.io)
+ */
+@Service
+public class GreetingService {
+ public String greet(String who) {
+ return String.format("hello, %s!", who);
+ }
+}
diff --git a/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/JerseyConfig.java b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/JerseyConfig.java
new file mode 100644
index 0000000..372ca86
--- /dev/null
+++ b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/JerseyConfig.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.hello.spring.annotations;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+
+/**
+ * A simple package based configuration
+ *
+ * @author Geoffroy Warin (http://geowarin.github.io)
+ */
+public class JerseyConfig extends ResourceConfig {
+
+ public JerseyConfig() {
+ register(RequestContextFilter.class);
+ packages("org.glassfish.jersey.examples.hello.spring.annotations");
+ register(LoggingFeature.class);
+ }
+}
diff --git a/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/NorwegianGoodbyeService.java b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/NorwegianGoodbyeService.java
new file mode 100644
index 0000000..ac5fe19
--- /dev/null
+++ b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/NorwegianGoodbyeService.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.hello.spring.annotations;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class NorwegianGoodbyeService implements GoodbyeService {
+
+ @Override
+ public String goodbye(final String who) {
+ return String.format("hadet, %s!", who);
+ }
+}
diff --git a/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/SpringAnnotationConfig.java b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/SpringAnnotationConfig.java
new file mode 100644
index 0000000..b0e63f0
--- /dev/null
+++ b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/SpringAnnotationConfig.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.hello.spring.annotations;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Spring configuration to include our services
+ *
+ * @author Geoffroy Warin (http://geowarin.github.io)
+ */
+@Configuration
+@ComponentScan(basePackageClasses = {GreetingService.class})
+public class SpringAnnotationConfig {
+}
diff --git a/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/SpringRequestResource.java b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/SpringRequestResource.java
new file mode 100644
index 0000000..c0dbbbe
--- /dev/null
+++ b/examples/helloworld-spring-annotations/src/main/java/org/glassfish/jersey/examples/hello/spring/annotations/SpringRequestResource.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.hello.spring.annotations;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import javax.inject.Singleton;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * Integration of jersey and spring.
+ * This rest controller is a singleton spring bean with autowired dependencies
+ * from spring
+ *
+ * @author Geoffroy Warin (http://geowarin.github.io)
+ */
+@Singleton
+@Path("spring-resource")
+@Service
+public class SpringRequestResource {
+
+ AtomicInteger counter = new AtomicInteger();
+
+ @Autowired
+ private GreetingService greetingService;
+
+ @Autowired
+ private List<GoodbyeService> goodbyeServicesList;
+ @Autowired
+ private Set<GoodbyeService> goodbyeServicesSet;
+
+ @Autowired
+ private List<GoodbyeService> goodbyeServicesIterable;
+
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getHello() {
+ return greetingService.greet("world " + counter.incrementAndGet());
+ }
+
+ private void checkIntegrity() {
+ final Iterator<GoodbyeService> it = goodbyeServicesIterable.iterator();
+ int i = 0;
+ while (it.hasNext()) {
+
+ final GoodbyeService s1 = it.next();
+ final GoodbyeService s2 = goodbyeServicesList.get(i);
+ if (s1 != s2) {
+ throw new ProcessingException("Instance of service s1 (" + s1.getClass()
+ + ") is not equal to service s2(" + s2.getClass() + ")");
+ }
+ i++;
+ }
+
+ if (goodbyeServicesList.size() != goodbyeServicesSet.size()) {
+ throw new ProcessingException("Size of set and size of the list differs. list=" + goodbyeServicesList.size()
+ + "; set=" + goodbyeServicesSet.size());
+ }
+ }
+
+ private GoodbyeService getService(Class<?> serviceClass) {
+ for (GoodbyeService service : goodbyeServicesList) {
+ if (serviceClass.isAssignableFrom(service.getClass())) {
+ return service;
+ }
+ }
+ return null;
+ }
+
+ @Path("goodbye")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getGoodbye() {
+ checkIntegrity();
+
+ final GoodbyeService goodbyeService = getService(EnglishGoodbyeService.class);
+ return goodbyeService.goodbye("cruel world");
+ }
+
+ @Path("norwegian-goodbye")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getNorwegianGoodbye() {
+ checkIntegrity();
+ return getService(NorwegianGoodbyeService.class).goodbye("på badet");
+ }
+}
diff --git a/examples/helloworld-spring-annotations/src/test/java/org/glassfish/jersey/examples/hello/spring/annotations/SpringRequestResourceTest.java b/examples/helloworld-spring-annotations/src/test/java/org/glassfish/jersey/examples/hello/spring/annotations/SpringRequestResourceTest.java
new file mode 100644
index 0000000..0f6fd4a
--- /dev/null
+++ b/examples/helloworld-spring-annotations/src/test/java/org/glassfish/jersey/examples/hello/spring/annotations/SpringRequestResourceTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.hello.spring.annotations;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+/**
+ * Testing our service with our annotation context being passed directly to jersey-spring
+ *
+ * @author Geoffroy Warin (http://geowarin.github.io)
+ */
+public class SpringRequestResourceTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ ApplicationContext context = new AnnotationConfigApplicationContext(SpringAnnotationConfig.class);
+ return new JerseyConfig().property("contextConfig", context);
+ }
+
+ @Test
+ public void testGreet() throws Exception {
+ final String greeting = target("spring-resource").request().get(String.class);
+ Assert.assertEquals("hello, world 1!", greeting);
+ final String greeting2 = target("spring-resource").request().get(String.class);
+ Assert.assertEquals("hello, world 2!", greeting2);
+ }
+
+ @Test
+ public void testGoodbye() {
+ final String goodbye = target("spring-resource").path("goodbye").request().get(String.class);
+ Assert.assertEquals("goodbye, cruel world!", goodbye);
+ final String norwegianGoodbye = target("spring-resource").path("norwegian-goodbye").request().get(String.class);
+ Assert.assertEquals("hadet, på badet!", norwegianGoodbye);
+ }
+}
diff --git a/examples/helloworld-spring-webapp/README.MD b/examples/helloworld-spring-webapp/README.MD
new file mode 100644
index 0000000..a5f07f7
--- /dev/null
+++ b/examples/helloworld-spring-webapp/README.MD
@@ -0,0 +1,43 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Jersey Spring Integration Spring Helloworld WebApp Example
+==========================================================
+
+This example demonstrates how to create simple Jersey application that
+utilises Spring 3 DI features, packaged into WAR file.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+------------------------------- | ------------------------- | --------------
+**_/jersey-hello_** | JerseyResource | GET
+**_/spring-hello_** | SpringRequestResource | GET
+**_/spring-singleton-hello_** | SpringSingletonResource | GET
+
+Application is configured by using web.xml, which registers
+[javax.ws.rs.core.Application](https://jax-rs-spec.java.net/nonav/2.0/apidocs/javax/ws/rs/core/Application.html)
+descendant to get classes and singletons from it (see class
+MyApplication).
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean package jetty:run
+
+This deploys current example using Jetty web server. Once deployed, the
+application is ready to serve the following resources:
+
+- <http://localhost:8080/helloworld-spring-webapp/jersey-hello>
+- <http://localhost:8080/helloworld-spring-webapp/spring-hello>
+- <http://localhost:8080/helloworld-spring-webapp/spring-singleton-hello>
\ No newline at end of file
diff --git a/examples/helloworld-spring-webapp/pom.xml b/examples/helloworld-spring-webapp/pom.xml
new file mode 100644
index 0000000..1ef882f
--- /dev/null
+++ b/examples/helloworld-spring-webapp/pom.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <!-- There is problem to run Spring example on GF - https://java.net/jira/browse/JERSEY-2032
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.2-SNAPSHOT</version>
+ </parent>
+ -->
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>helloworld-spring-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-helloworld-spring-webapp</name>
+
+ <description>Spring 4 Integration Jersey Example</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ <version>${spring4.version}</version>
+ <scope>compile</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.2</version>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-spring4</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>servlet-api-2.5</artifactId>
+ <version>${jetty.servlet.api.25.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Run the application using "mvn jetty:run" to deploy to Jetty-->
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>maven-jetty-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/CustomExceptionMapper.java b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/CustomExceptionMapper.java
new file mode 100644
index 0000000..ec1e6b4
--- /dev/null
+++ b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/CustomExceptionMapper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.spring;
+
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Exception mapper to convert {@link IllegalArgumentException} into a textual response.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ */
+@Provider
+public class CustomExceptionMapper implements ExceptionMapper<IllegalArgumentException> {
+
+ @Override
+ public Response toResponse(IllegalArgumentException exception) {
+ return Response.ok("Illegal Argument Exception Caught").build();
+ }
+}
diff --git a/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/DateTimeService.java b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/DateTimeService.java
new file mode 100644
index 0000000..2bc3a42
--- /dev/null
+++ b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/DateTimeService.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.spring;
+
+import java.util.Date;
+
+/**
+ * Simple date service that provides actual time and date info.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ */
+public class DateTimeService {
+
+ /**
+ * Get current date and time.
+ *
+ * @return current date.
+ */
+ public Date getDateTime() {
+ return new Date();
+ }
+}
diff --git a/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/GreetingService.java b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/GreetingService.java
new file mode 100644
index 0000000..ca9f719
--- /dev/null
+++ b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/GreetingService.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.spring;
+
+/**
+ * Simple greeting service.
+ *
+ * @author Marko Asplund (marko.asplund at oracle.com)
+ */
+public interface GreetingService {
+
+ /**
+ * Workout a greeting.
+ *
+ * @param who to greet.
+ * @return greeting.
+ */
+ String greet(String who);
+}
diff --git a/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/GreetingServiceImpl.java b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/GreetingServiceImpl.java
new file mode 100644
index 0000000..af54d2a
--- /dev/null
+++ b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/GreetingServiceImpl.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.spring;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Simple {@link GreetingService} implementation to just say hello.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class GreetingServiceImpl implements GreetingService {
+
+ @Autowired
+ private HttpServletRequest servletRequest;
+
+ @Override
+ public String greet(String who) {
+ final String serverName = servletRequest.getServerName();
+ return String.format("hello, %s! Greetings from server %s!", who, serverName);
+ }
+
+}
diff --git a/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/JerseyResource.java b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/JerseyResource.java
new file mode 100644
index 0000000..6c0439c
--- /dev/null
+++ b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/JerseyResource.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.spring;
+
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Jersey Spring integration example.
+ * Demonstrate how to inject a Spring bean into a Jersey managed JAX-RS resource class.
+ *
+ * @author Marko Asplund (marko.asplund at gmail.com)
+ */
+@Path("jersey-hello")
+public class JerseyResource {
+ private static final Logger LOGGER = Logger.getLogger(JerseyResource.class.getName());
+
+ @Autowired
+ private GreetingService greetingService;
+
+ @Inject
+ private DateTimeService timeService;
+
+ public JerseyResource() {
+ LOGGER.fine("HelloWorldResource()");
+ }
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getHello() {
+ return String.format("%s: %s", timeService.getDateTime(), greetingService.greet("world"));
+ }
+
+}
diff --git a/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/MyApplication.java b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/MyApplication.java
new file mode 100644
index 0000000..f07b225
--- /dev/null
+++ b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/MyApplication.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.spring;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+
+/**
+ * Spring HelloWorld Web Application configuration.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class MyApplication extends ResourceConfig {
+
+ /**
+ * Register JAX-RS application components.
+ */
+ public MyApplication() {
+ register(RequestContextFilter.class);
+ register(JerseyResource.class);
+ register(SpringSingletonResource.class);
+ register(SpringRequestResource.class);
+ register(CustomExceptionMapper.class);
+ }
+}
diff --git a/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/SpringRequestResource.java b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/SpringRequestResource.java
new file mode 100644
index 0000000..8987a85
--- /dev/null
+++ b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/SpringRequestResource.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.spring;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Jersey Spring integration example.
+ * Demonstrate how to use Spring managed JAX-RS resource class with request scope (+ Spring bean DI).
+ *
+ * @author Marko Asplund (marko.asplund at gmail.com)
+ */
+@Path("spring-hello")
+@Component
+public class SpringRequestResource {
+
+ @Autowired
+ private GreetingService greetingService;
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getHello() {
+ return greetingService.greet("world");
+ }
+}
diff --git a/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/SpringSingletonResource.java b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/SpringSingletonResource.java
new file mode 100644
index 0000000..de91d63
--- /dev/null
+++ b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/SpringSingletonResource.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.spring;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+
+import javax.inject.Singleton;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Jersey Spring integration example.
+ * Demonstrate how to use Spring managed JAX-RS resource class with singleton scope (+ Spring bean DI).
+ *
+ * @author Marko Asplund (marko.asplund at gmail.com)
+ */
+@Path("spring-singleton-hello")
+@Component
+@Singleton
+public class SpringSingletonResource {
+
+ private final AtomicInteger counter = new AtomicInteger();
+
+ @Autowired
+ private GreetingService greetingService;
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getHello(@Context HttpHeaders headers, @QueryParam("p1") String p1) {
+ if ("foobar".equals(p1)) {
+ throw new IllegalArgumentException("foobar is illegal");
+ }
+ return String.format("%d: %s", counter.incrementAndGet(), greetingService.greet("world"));
+ }
+}
diff --git a/examples/helloworld-spring-webapp/src/main/resources/applicationContext.xml b/examples/helloworld-spring-webapp/src/main/resources/applicationContext.xml
new file mode 100644
index 0000000..0a3ffd1
--- /dev/null
+++ b/examples/helloworld-spring-webapp/src/main/resources/applicationContext.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="greetingService" class="org.glassfish.jersey.examples.helloworld.spring.GreetingServiceImpl"/>
+
+ <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
+
+ <bean class="org.glassfish.jersey.examples.helloworld.spring.DateTimeService" scope="request"/>
+
+ <bean class="org.glassfish.jersey.examples.helloworld.spring.SpringSingletonResource"/>
+
+ <bean class="org.glassfish.jersey.examples.helloworld.spring.SpringRequestResource" scope="request"/>
+
+ <bean class="org.glassfish.jersey.examples.helloworld.spring.CustomExceptionMapper"/>
+
+</beans>
diff --git a/examples/helloworld-spring-webapp/src/main/webapp/WEB-INF/web.xml b/examples/helloworld-spring-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..c3bb6a5
--- /dev/null
+++ b/examples/helloworld-spring-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
+
+ <module-name>helloworld-spring</module-name>
+
+ <listener>
+ <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+ </listener>
+
+ <context-param>
+ <param-name>contextConfigLocation</param-name>
+ <param-value>classpath:applicationContext.xml</param-value>
+ </context-param>
+
+ <servlet>
+ <servlet-name>SpringApplication</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.glassfish.jersey.examples.helloworld.spring.MyApplication</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>SpringApplication</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+</web-app>
diff --git a/examples/helloworld-webapp/README.MD b/examples/helloworld-webapp/README.MD
new file mode 100644
index 0000000..e449fce
--- /dev/null
+++ b/examples/helloworld-webapp/README.MD
@@ -0,0 +1,38 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Helloworld webapp Example
+=========================
+
+This example demonstrates how to create simple Jersey application,
+package it into WAR file and how to run it using [Grizzly
+container](http://grizzly.java.net).
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+------------------- | -------------------- | --------------
+**_/helloworld_** | HelloWorldResource | GET
+
+Application is configured by using web.xml, which registers
+[javax.ws.rs.core.Application](https://jax-rs-spec.java.net/nonav/2.0/apidocs/javax/ws/rs/core/Application.html)
+descendant to get classes and singletons from it (see class MyApplication).
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container. You can access the application at:
+
+- <http://localhost:8080/helloworld-webapp/helloworld>
diff --git a/examples/helloworld-webapp/pom.xml b/examples/helloworld-webapp/pom.xml
new file mode 100644
index 0000000..ad575de
--- /dev/null
+++ b/examples/helloworld-webapp/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>helloworld-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-helloworld-webapp</name>
+
+ <description>Jersey annotated resource class "Hello world" example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>${servlet4.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-servlet</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Run the application using "mvn jetty:run" to deploy to Jetty-->
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>maven-jetty-plugin</artifactId>
+ </plugin>
+ <!-- Run the application using "mvn exec:java" to deploy to Grizzly Servlet-->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.helloworld.webapp.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/helloworld-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/webapp/App.java b/examples/helloworld-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/webapp/App.java
new file mode 100644
index 0000000..85618ce
--- /dev/null
+++ b/examples/helloworld-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/webapp/App.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.webapp;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.servlet.GrizzlyWebContainerFactory;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.servlet.ServletContainer;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/helloworld-webapp/");
+ public static final String ROOT_PATH = "helloworld";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Hello World\" Jersey Example App");
+
+ Map<String, String> initParams = new HashMap<>();
+ initParams.put(
+ ServerProperties.PROVIDER_PACKAGES,
+ HelloWorldResource.class.getPackage().getName());
+ final HttpServer server = GrizzlyWebContainerFactory.create(BASE_URI, ServletContainer.class, initParams);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+
+ System.out.println(String.format("Application started.%nTry out %s%s%nStop the application using CTRL+C",
+ BASE_URI, ROOT_PATH));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+}
diff --git a/examples/helloworld-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/webapp/HelloWorldResource.java b/examples/helloworld-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/webapp/HelloWorldResource.java
new file mode 100644
index 0000000..3669b69
--- /dev/null
+++ b/examples/helloworld-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/webapp/HelloWorldResource.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.webapp;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Path("helloworld")
+public class HelloWorldResource {
+
+ @GET
+ @Produces("text/plain")
+ public String getHello() {
+ return "Hello World!";
+ }
+
+}
diff --git a/examples/helloworld-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/webapp/MyApplication.java b/examples/helloworld-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/webapp/MyApplication.java
new file mode 100644
index 0000000..5e929e5
--- /dev/null
+++ b/examples/helloworld-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/webapp/MyApplication.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.webapp;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@ApplicationPath("/")
+public class MyApplication extends Application {
+ @Override
+ public Set<Class<?>> getClasses() {
+ final Set<Class<?>> classes = new HashSet<Class<?>>();
+ // register root resource
+ classes.add(HelloWorldResource.class);
+ return classes;
+ }
+}
diff --git a/examples/helloworld-webapp/src/main/webapp/WEB-INF/glassfish-web.xml b/examples/helloworld-webapp/src/main/webapp/WEB-INF/glassfish-web.xml
new file mode 100644
index 0000000..683dff4
--- /dev/null
+++ b/examples/helloworld-webapp/src/main/webapp/WEB-INF/glassfish-web.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
+<glassfish-web-app error-url="">
+ <context-root>/helloworld-webapp</context-root>
+ <class-loader delegate="true" />
+ <jsp-config>
+ <property name="classdebuginfo" value="true">
+ <description>Enable debug info compilation in the generated servlet class</description>
+ </property>
+ <property name="keepgenerated" value="true">
+ <description>Keep a copy of the generated servlet class' java code.</description>
+ </property>
+ <property name="mappedfile" value="true">
+ <description>Maintain a one-to-one correspondence between static content and the generated servlet class' java code</description>
+ </property>
+ </jsp-config>
+</glassfish-web-app>
diff --git a/examples/helloworld-webapp/src/main/webapp/WEB-INF/web.xml b/examples/helloworld-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..1e484b9
--- /dev/null
+++ b/examples/helloworld-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+ <servlet>
+ <servlet-name>org.glassfish.jersey.examples.helloworld_servlet.MyApplication</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.glassfish.jersey.examples.helloworld.webapp.MyApplication</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>org.glassfish.jersey.examples.helloworld_servlet.MyApplication</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+</web-app>
diff --git a/examples/helloworld-webapp/src/test/java/org/glassfish/jersey/examples/helloworld/webapp/HelloWorldTest.java b/examples/helloworld-webapp/src/test/java/org/glassfish/jersey/examples/helloworld/webapp/HelloWorldTest.java
new file mode 100644
index 0000000..4da4a9f
--- /dev/null
+++ b/examples/helloworld-webapp/src/test/java/org/glassfish/jersey/examples/helloworld/webapp/HelloWorldTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.webapp;
+
+import java.net.URI;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Simple test to check "Hello World!" is being returned from the helloworld resource.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class HelloWorldTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ return new MyApplication();
+ }
+
+ @Override
+ protected URI getBaseUri() {
+ return UriBuilder.fromUri(super.getBaseUri()).path("helloworld-webapp").build();
+ }
+
+ @Test
+ public void testClientStringResponse() {
+ String s = target().path(App.ROOT_PATH).request().get(String.class);
+ assertEquals("Hello World!", s);
+ }
+}
+
diff --git a/examples/helloworld-weld/README.MD b/examples/helloworld-weld/README.MD
new file mode 100644
index 0000000..e139e21
--- /dev/null
+++ b/examples/helloworld-weld/README.MD
@@ -0,0 +1,40 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Jersey Weld Integration Jersey Weld on Grizzly Example
+======================================================
+
+This example demonstrates how to develop RESTful web service with CDI
+managed beans and Grizzly HTTP container.
+
+We suggest you inspect the example source code to get better
+understanding on various CDI features utilized there. Following table
+contains some of the published resources that you might want to check.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URL | Description | Expected Results
+---------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------
+**_/helloworld?name=Bill_** | A hello-world managed bean | Hello Bill
+**_/app/count_** | Application scoped resource | request count (incremented with every other request)
+**_/req/app/counter_** | Request scoped resource | links to above request counter
+**_/req/parameterized-async?q=echo%20this_** | Asynchronous echo resource method that is being JAX-RS intercepted | echo this
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container. You can access the application at:
+
+- <http://localhost:8080/weld>
\ No newline at end of file
diff --git a/examples/helloworld-weld/pom.xml b/examples/helloworld-weld/pom.xml
new file mode 100644
index 0000000..44adf89
--- /dev/null
+++ b/examples/helloworld-weld/pom.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>helloworld-weld</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-helloworld-weld</name>
+
+ <description>Jersey annotated resource class "Hello world" example with Weld support.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-util</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.cdi</groupId>
+ <artifactId>jersey-weld2-se</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.cdi</groupId>
+ <artifactId>jersey-cdi1x</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.helloworld.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>delayed-strategy-skip-test</id>
+ <activation>
+ <property>
+ <name>org.glassfish.jersey.injection.manager.strategy</name>
+ <value>delayed</value>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skipTests>true</skipTests>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/App.java b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/App.java
new file mode 100644
index 0000000..d9ed128
--- /dev/null
+++ b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/App.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Set;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.annotation.ManagedBean;
+import javax.enterprise.context.ApplicationScoped;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.jboss.weld.environment.se.Weld;
+
+/**
+ * Main Java application. Used to bootstrap Weld container and start Grizzly HTTP container.
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/weld/");
+ public static final String ROOT_PATH = "application.wadl";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Hello World\" Jersey Example Weld App");
+
+ final Weld weld = new Weld();
+ weld.initialize();
+
+ final ResourceConfig resourceConfig = createJaxRsApp();
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ weld.shutdown();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.\nTry out %s%s\nStop the application using CTRL+C",
+ BASE_URI, ROOT_PATH));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ /**
+ * JAX-RS application defined as a CDI bean.
+ */
+ @ManagedBean
+ public static class JaxRsApplication extends Application {
+
+ @Context
+ UriInfo uInfo;
+
+ static final Set<Class<?>> appClasses = new HashSet<>();
+
+ static {
+ appClasses.add(HelloWorldResource.class);
+ appClasses.add(AppScopedResource.class);
+ appClasses.add(RequestScopedResource.class);
+ appClasses.add(CustomInterceptor.class);
+ }
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ return appClasses;
+ }
+ }
+
+ /**
+ * Create JAX-RS application. The same one is used also in the tests.
+ *
+ * @return Jersey's resource configuration of the Weld application.
+ */
+ public static ResourceConfig createJaxRsApp() {
+ return ResourceConfig.forApplicationClass(JaxRsApplication.class);
+ }
+}
diff --git a/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/AppScopedResource.java b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/AppScopedResource.java
new file mode 100644
index 0000000..fa231fc
--- /dev/null
+++ b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/AppScopedResource.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * Application scoped CDI based resource.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("app")
+@ApplicationScoped
+public class AppScopedResource {
+
+ AtomicInteger counter = new AtomicInteger();
+
+ @GET
+ @Path("count")
+ @Produces("text/plain")
+ public int getCount() {
+ return counter.incrementAndGet();
+ }
+}
diff --git a/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/CustomInterceptor.java b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/CustomInterceptor.java
new file mode 100644
index 0000000..342a7c7
--- /dev/null
+++ b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/CustomInterceptor.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import java.io.IOException;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import javax.inject.Inject;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.ext.WriterInterceptor;
+import javax.ws.rs.ext.WriterInterceptorContext;
+
+/**
+ * CDI based JAX-RS interceptor that re-writes the original output
+ * with request ID obtained from another CDI bean.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@ResponseBodyFromCdiBean
+@ApplicationScoped
+public class CustomInterceptor implements WriterInterceptor {
+
+ // CDI injected proxy
+ @Inject RequestScopedBean bean;
+
+ @Override
+ public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
+ final String requestId = bean.getRequestId();
+ context.getOutputStream().write(requestId.getBytes());
+ }
+}
diff --git a/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/HelloWorldResource.java b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/HelloWorldResource.java
new file mode 100644
index 0000000..aa76f50
--- /dev/null
+++ b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/HelloWorldResource.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import javax.enterprise.context.RequestScoped;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+
+/**
+ * Hello-world JAX-RS resource implemented as CDI bean.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("helloworld")
+@RequestScoped
+public class HelloWorldResource {
+
+ @QueryParam("name")
+ String name;
+
+ @GET
+ @Produces("text/plain")
+ public String getHello() {
+ return String.format("Hello %s", name);
+ }
+}
diff --git a/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/RequestScopedBean.java b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/RequestScopedBean.java
new file mode 100644
index 0000000..8cac041
--- /dev/null
+++ b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/RequestScopedBean.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import javax.enterprise.context.RequestScoped;
+
+/**
+ * Request scoped CDI bean. Serves as a storage
+ * for request scoped data to demonstrate that CDI based
+ * JAX-RS interceptor, {@link CustomInterceptor}, could
+ * use CDI means to obtain JAX-RS request data.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@RequestScoped
+public class RequestScopedBean {
+
+ private String requestId;
+
+ /**
+ * Request ID setter.
+ *
+ * @param requestId
+ */
+ public void setRequestId(String requestId) {
+ this.requestId = requestId;
+ }
+
+ /**
+ * Get me current request ID.
+ *
+ * @return request id.
+ */
+ public String getRequestId() {
+ return requestId;
+ }
+}
diff --git a/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/RequestScopedResource.java b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/RequestScopedResource.java
new file mode 100644
index 0000000..135b110
--- /dev/null
+++ b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/RequestScopedResource.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+
+import javax.enterprise.context.RequestScoped;
+import javax.inject.Inject;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * JAX-RS resource class backed by a request scoped CDI bean.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@RequestScoped
+@Path("req")
+public class RequestScopedResource {
+
+ @Inject
+ AppScopedResource appResource;
+
+ @Inject
+ RequestScopedBean bean;
+
+ @GET
+ @Path("app/counter")
+ public int getCounter() {
+ return appResource.getCount();
+ }
+
+ @GET
+ @Path("myself")
+ public String getMyself() {
+ return this.toString();
+ }
+
+ @GET
+ @Path("parameterized")
+ @ResponseBodyFromCdiBean
+ public String interceptedParameterized(@QueryParam("q") String q) {
+ bean.setRequestId(q);
+ return "does not matter";
+ }
+
+ @GET
+ @Path("straight")
+ public String parameterizedStraight(@QueryParam("q") String q) {
+ return "straight: " + q;
+ }
+
+ private static final Executor executor = Executors.newCachedThreadPool();
+
+ @GET
+ @Path("parameterized-async")
+ @ResponseBodyFromCdiBean
+ public void interceptedParameterizedAsync(@QueryParam("q") final String q, @Suspended final AsyncResponse response) {
+ bean.setRequestId(q);
+ executor.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(RequestScopedResource.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ response.resume("this will never make it to the client");
+ }
+ });
+ }
+
+ @Context
+ UriInfo uriInfo;
+
+ @Inject App.JaxRsApplication jaxRsApplication;
+
+ @GET
+ @Path("ui/jax-rs-field/{d}")
+ public String getJaxRsInjectedUIUri() {
+
+ if (uriInfo == jaxRsApplication.uInfo) {
+ throw new IllegalStateException("UriInfo injected into req scoped cdi bean should not get proxied.");
+ }
+
+ return uriInfo.getRequestUri().toString();
+ }
+
+ @GET
+ @Path("ui/jax-rs-app-field/{d}")
+ public String getCdiInjectedJaxRsAppUri() {
+ return jaxRsApplication.uInfo.getRequestUri().toString();
+ }
+}
diff --git a/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/ResponseBodyFromCdiBean.java b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/ResponseBodyFromCdiBean.java
new file mode 100644
index 0000000..f403648
--- /dev/null
+++ b/examples/helloworld-weld/src/main/java/org/glassfish/jersey/examples/helloworld/ResponseBodyFromCdiBean.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import javax.ws.rs.NameBinding;
+
+/**
+ * Binds {@link org.glassfish.jersey.examples.helloworld.CustomInterceptor} with resource methods that should return modified
+ * entity than the one returned from the method.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Retention(RUNTIME)
+@Target({METHOD, TYPE})
+@NameBinding
+public @interface ResponseBodyFromCdiBean {
+}
diff --git a/examples/helloworld-weld/src/main/resources/META-INF/beans.xml b/examples/helloworld-weld/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000..5d361e9
--- /dev/null
+++ b/examples/helloworld-weld/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<beans/>
diff --git a/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/AppScopedResourceTest.java b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/AppScopedResourceTest.java
new file mode 100644
index 0000000..33dc579
--- /dev/null
+++ b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/AppScopedResourceTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.jboss.weld.environment.se.Weld;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test for the {@link AppScopedResource} JAX-RS resource class.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class AppScopedResourceTest extends JerseyTest {
+
+ Weld weld;
+
+ @Override
+ public void setUp() throws Exception {
+ weld = new Weld();
+ weld.initialize();
+ super.setUp();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ weld.shutdown();
+ super.tearDown();
+ }
+
+ @Override
+ protected ResourceConfig configure() {
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.jdkhttp.JdkHttpServerTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.simple.SimpleTestContainerFactory
+ enable(TestProperties.LOG_TRAFFIC);
+ // enable(TestProperties.DUMP_ENTITY);
+ return App.createJaxRsApp();
+ }
+
+ @Test
+ public void testCounter() {
+ final Invocation.Builder counter = target().path("app/count").request("text/plain");
+
+ Response response = counter.get();
+
+ assertEquals(200, response.getStatus());
+ int c1 = response.readEntity(Integer.class);
+ int c2 = counter.get(Integer.class);
+ int c3 = counter.get(Integer.class);
+ assertTrue(c1 < c2);
+ assertTrue(c2 < c3);
+ }
+}
diff --git a/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java
new file mode 100644
index 0000000..df6a311
--- /dev/null
+++ b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.jboss.weld.environment.se.Weld;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test for the {@link HelloWorldResource} JAX-RS resource class.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class HelloWorldTest extends JerseyTest {
+
+ private Weld weld;
+
+ @Override
+ public void setUp() throws Exception {
+ weld = new Weld();
+ weld.initialize();
+ super.setUp();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ weld.shutdown();
+ super.tearDown();
+ }
+
+ @Override
+ protected ResourceConfig configure() {
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.jdkhttp.JdkHttpServerTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.simple.SimpleTestContainerFactory
+ enable(TestProperties.LOG_TRAFFIC);
+ // enable(TestProperties.DUMP_ENTITY);
+ return App.createJaxRsApp();
+ }
+
+ @Test
+ public void testHelloWorld() {
+ Response response = target().path("helloworld").queryParam("name", "Josef").request("text/plain").get();
+ assertEquals(200, response.getStatus());
+ assertEquals(String.format("Hello %s", "Josef"), response.readEntity(String.class));
+ }
+}
diff --git a/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopeAlignmentTest.java b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopeAlignmentTest.java
new file mode 100644
index 0000000..b6be123
--- /dev/null
+++ b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopeAlignmentTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.ws.rs.client.WebTarget;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.jboss.weld.environment.se.Weld;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Test for the request scoped managed bean resource.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class RequestScopeAlignmentTest extends JerseyTest {
+
+ static Weld weld;
+
+ @BeforeClass
+ public static void before() throws Exception {
+ weld = new Weld();
+ weld.initialize();
+ }
+
+ @AfterClass
+ public static void after() throws Exception {
+ weld.shutdown();
+ }
+
+ @Override
+ protected ResourceConfig configure() {
+ return App.createJaxRsApp();
+ }
+
+ @Test
+ public void testUriInfoPropagatesToApp() {
+
+ for (String d : new String[]{"one", "two", "three"}) {
+
+ final WebTarget fieldTarget = target().path("req/ui/jax-rs-field").path(d);
+ final WebTarget appFieldTarget = target().path("req/ui/jax-rs-app-field").path(d);
+
+ String f = fieldTarget.request().get(String.class);
+ assertThat(f, containsString(fieldTarget.getUri().toString()));
+ String af = appFieldTarget.request().get(String.class);
+ assertThat(af, containsString(appFieldTarget.getUri().toString()));
+ }
+ }
+}
diff --git a/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopedResourceTest.java b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopedResourceTest.java
new file mode 100644
index 0000000..f6a4fa9
--- /dev/null
+++ b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopedResourceTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import java.util.Iterator;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.util.runner.ConcurrentParameterizedRunner;
+
+import org.jboss.weld.environment.se.Weld;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Test request scoped resource. Number of various requests will be made in parallel
+ * against a single Grizzly instance. This is to ensure server side external request scope
+ * binding does not mix different request data.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@RunWith(ConcurrentParameterizedRunner.class)
+public class RequestScopedResourceTest extends JerseyTest {
+
+ // Total number of requests to make
+ static final int REQUEST_COUNT = 1000;
+ // basis for test data sequence
+ static final AtomicInteger dataFeed = new AtomicInteger();
+
+ // to help us randomily select resource method to test
+ static final Random RANDOMIZER = new Random();
+
+ // our Weld container instance
+ static Weld weld;
+
+ /**
+ * Take test data sequence from here
+ *
+ * @return iterable test input data
+ */
+ @Parameterized.Parameters
+ public static Iterable<Object[]> data() {
+ return new Iterable<Object[]>() {
+
+ @Override
+ public Iterator<Object[]> iterator() {
+ return new Iterator<Object[]>() {
+
+ @Override
+ public boolean hasNext() {
+ return dataFeed.get() < REQUEST_COUNT;
+ }
+
+ @Override
+ public Object[] next() {
+ Object[] result = new Object[1];
+ int nextValue = dataFeed.getAndIncrement();
+ result[0] = String.format("%02d", nextValue);
+ return result;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ @BeforeClass
+ public static void before() throws Exception {
+ weld = new Weld();
+ weld.initialize();
+ }
+
+ @AfterClass
+ public static void after() throws Exception {
+ weld.shutdown();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ System.out.printf("SYNC: %d, ASYNC: %d, STRAIGHT: %d%n",
+ parameterizedCounter.intValue(), parameterizedAsyncCounter.intValue(), straightCounter.intValue());
+ }
+
+ @Override
+ protected ResourceConfig configure() {
+ // enable(TestProperties.LOG_TRAFFIC);
+ return App.createJaxRsApp();
+ }
+
+ // we want to keep some statistics
+ final AtomicInteger parameterizedCounter = new AtomicInteger(0);
+ final AtomicInteger parameterizedAsyncCounter = new AtomicInteger(0);
+ final AtomicInteger straightCounter = new AtomicInteger(0);
+
+ @Test
+ public void testRequestScopedResource(final String param) {
+
+ String path;
+ String expected = param;
+
+ // select one of the three resource methods available
+ switch (RANDOMIZER.nextInt(3)) {
+ case 0:
+ path = "req/parameterized";
+ parameterizedCounter.incrementAndGet();
+ break;
+ case 1:
+ path = "req/parameterized-async";
+ parameterizedAsyncCounter.incrementAndGet();
+ break;
+ default:
+ path = "req/straight";
+ expected = String.format("straight: %s", param);
+ straightCounter.incrementAndGet();
+ break;
+ }
+
+ final Response response = target().path(path).queryParam("q", param).request("text/plain").get();
+
+ assertNotNull(String.format("Request failed for %s", path), response);
+ assertEquals(200, response.getStatus());
+ assertEquals(expected, response.readEntity(String.class));
+ }
+}
diff --git a/examples/helloworld/README.MD b/examples/helloworld/README.MD
new file mode 100644
index 0000000..350d991
--- /dev/null
+++ b/examples/helloworld/README.MD
@@ -0,0 +1,32 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Hello World Example
+===================
+
+This example demonstrates Hello World example. JAX-RS resource returns the usual text
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Notes
+-------------------- | ------------------- | ------------ | --------------------------------------------------------
+**_/helloworld_** | HelloWorldResource | GET | Returns `Hello World!`
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
+
+- <http://localhost:8080/base/helloworld>
diff --git a/examples/helloworld/pom.xml b/examples/helloworld/pom.xml
new file mode 100644
index 0000000..e716587
--- /dev/null
+++ b/examples/helloworld/pom.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>helloworld</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-helloworld</name>
+
+ <description>Jersey annotated resource class "Hello world" example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-util</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.helloworld.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/helloworld/src/main/java/org/glassfish/jersey/examples/helloworld/App.java b/examples/helloworld/src/main/java/org/glassfish/jersey/examples/helloworld/App.java
new file mode 100644
index 0000000..ae3ec2b
--- /dev/null
+++ b/examples/helloworld/src/main/java/org/glassfish/jersey/examples/helloworld/App.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Hello world!
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/base/");
+ public static final String ROOT_PATH = "helloworld";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Hello World\" Jersey Example App");
+
+ final ResourceConfig resourceConfig = new ResourceConfig(HelloWorldResource.class);
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.\nTry out %s%s\nStop the application using CTRL+C",
+ BASE_URI, ROOT_PATH));
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+}
diff --git a/examples/helloworld/src/main/java/org/glassfish/jersey/examples/helloworld/HelloWorldResource.java b/examples/helloworld/src/main/java/org/glassfish/jersey/examples/helloworld/HelloWorldResource.java
new file mode 100644
index 0000000..f1326e5
--- /dev/null
+++ b/examples/helloworld/src/main/java/org/glassfish/jersey/examples/helloworld/HelloWorldResource.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("helloworld")
+public class HelloWorldResource {
+ public static final String CLICHED_MESSAGE = "Hello World!";
+
+ @GET
+ @Produces("text/plain")
+ public String getHello() {
+ return CLICHED_MESSAGE;
+ }
+
+}
diff --git a/examples/helloworld/src/test/java/org/glassfish/jersey/examples/helloworld/CustomLoggingFilter.java b/examples/helloworld/src/test/java/org/glassfish/jersey/examples/helloworld/CustomLoggingFilter.java
new file mode 100644
index 0000000..4e04d46
--- /dev/null
+++ b/examples/helloworld/src/test/java/org/glassfish/jersey/examples/helloworld/CustomLoggingFilter.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import java.io.IOException;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Custom logging filter.
+ *
+ * @author Santiago Pericas-Geertsen (santiago.pericasgeertsen at oracle.com)
+ */
+public class CustomLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter,
+ ClientRequestFilter, ClientResponseFilter {
+
+ static int preFilterCalled = 0;
+ static int postFilterCalled = 0;
+
+ @Override
+ public void filter(ClientRequestContext context) throws IOException {
+ System.out.println("CustomLoggingFilter.preFilter called");
+ assertEquals(context.getConfiguration().getProperty("foo"), "bar");
+ preFilterCalled++;
+ }
+
+ @Override
+ public void filter(ClientRequestContext context, ClientResponseContext clientResponseContext) throws IOException {
+ System.out.println("CustomLoggingFilter.postFilter called");
+ assertEquals(context.getConfiguration().getProperty("foo"), "bar");
+ postFilterCalled++;
+ }
+
+ @Override
+ public void filter(ContainerRequestContext context) throws IOException {
+ System.out.println("CustomLoggingFilter.preFilter called");
+ assertEquals(context.getProperty("foo"), "bar");
+ preFilterCalled++;
+ }
+
+ @Override
+ public void filter(ContainerRequestContext context, ContainerResponseContext containerResponseContext) throws IOException {
+ System.out.println("CustomLoggingFilter.postFilter called");
+ assertEquals(context.getProperty("foo"), "bar");
+ postFilterCalled++;
+ }
+}
+
diff --git a/examples/helloworld/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java b/examples/helloworld/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java
new file mode 100644
index 0000000..538c212
--- /dev/null
+++ b/examples/helloworld/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld;
+
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.glassfish.jersey.test.util.runner.ConcurrentRunner;
+import org.glassfish.jersey.test.util.runner.RunSeparately;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(ConcurrentRunner.class)
+public class HelloWorldTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.jdkhttp.JdkHttpServerTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.simple.SimpleTestContainerFactory
+ enable(TestProperties.LOG_TRAFFIC);
+ // enable(TestProperties.DUMP_ENTITY);
+ return new ResourceConfig(HelloWorldResource.class);
+ }
+
+// Uncomment to use Grizzly async client
+// @Override
+// protected void configureClient(ClientConfig clientConfig) {
+// clientConfig.connector(new GrizzlyConnector(clientConfig));
+// }
+
+ @Test
+ @Ignore("not compatible with test framework (doesn't use client())")
+ public void testHelloWorld() throws Exception {
+ URL getUrl = UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).build().toURL();
+ HttpURLConnection connection = (HttpURLConnection) getUrl.openConnection();
+ try {
+ connection.setDoOutput(true);
+ connection.setInstanceFollowRedirects(false);
+ connection.setRequestMethod("GET");
+ connection.setRequestProperty("Content-Type", "text/plain");
+ assertEquals(HttpURLConnection.HTTP_OK, connection.getResponseCode());
+ } finally {
+ connection.disconnect();
+ }
+ }
+
+ @Test
+ public void testConnection() {
+ Response response = target().path(App.ROOT_PATH).request("text/plain").get();
+ assertEquals(200, response.getStatus());
+ }
+
+ @Test
+ public void testClientStringResponse() {
+ String s = target().path(App.ROOT_PATH).request().get(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+ }
+
+ @Test
+ public void testAsyncClientRequests() throws InterruptedException {
+ final int REQUESTS = 10;
+ final CountDownLatch latch = new CountDownLatch(REQUESTS);
+ final long tic = System.currentTimeMillis();
+ for (int i = 0; i < REQUESTS; i++) {
+ final int id = i;
+ target().path(App.ROOT_PATH).request().async().get(new InvocationCallback<Response>() {
+ @Override
+ public void completed(Response response) {
+ try {
+ final String result = response.readEntity(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, result);
+ } finally {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void failed(Throwable error) {
+ error.printStackTrace();
+ latch.countDown();
+ }
+ });
+ }
+ latch.await(10 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS);
+ final long toc = System.currentTimeMillis();
+ Logger.getLogger(HelloWorldTest.class.getName()).info("Executed in: " + (toc - tic));
+ }
+
+ @Test
+ public void testHead() {
+ Response response = target().path(App.ROOT_PATH).request().head();
+ assertEquals(200, response.getStatus());
+ assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType());
+ }
+
+ @Test
+ public void testFooBarOptions() {
+ Response response = target().path(App.ROOT_PATH).request().header("Accept", "foo/bar").options();
+ assertEquals(200, response.getStatus());
+ final String allowHeader = response.getHeaderString("Allow");
+ _checkAllowContent(allowHeader);
+ assertEquals("foo/bar", response.getMediaType().toString());
+ assertEquals(0, response.getLength());
+ }
+
+ @Test
+ public void testTextPlainOptions() {
+ Response response = target().path(App.ROOT_PATH).request().header("Accept", MediaType.TEXT_PLAIN).options();
+ assertEquals(200, response.getStatus());
+ final String allowHeader = response.getHeaderString("Allow");
+ _checkAllowContent(allowHeader);
+ assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType());
+ final String responseBody = response.readEntity(String.class);
+ _checkAllowContent(responseBody);
+ }
+
+ private void _checkAllowContent(final String content) {
+ assertTrue(content.contains("GET"));
+ assertTrue(content.contains("HEAD"));
+ assertTrue(content.contains("OPTIONS"));
+ }
+
+ @Test
+ public void testMissingResourceNotFound() {
+ Response response;
+
+ response = target().path(App.ROOT_PATH + "arbitrary").request().get();
+ assertEquals(404, response.getStatus());
+
+ response = target().path(App.ROOT_PATH).path("arbitrary").request().get();
+ assertEquals(404, response.getStatus());
+ }
+
+ @Test
+ @RunSeparately
+ public void testLoggingFilterClientClass() {
+ Client client = client();
+ client.register(CustomLoggingFilter.class).property("foo", "bar");
+ CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+ String s = target().path(App.ROOT_PATH).request().get(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+ assertEquals(1, CustomLoggingFilter.preFilterCalled);
+ assertEquals(1, CustomLoggingFilter.postFilterCalled);
+ }
+
+ @Test
+ @RunSeparately
+ public void testLoggingFilterClientInstance() {
+ Client client = client();
+ client.register(new CustomLoggingFilter()).property("foo", "bar");
+ CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+ String s = target().path(App.ROOT_PATH).request().get(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+ assertEquals(1, CustomLoggingFilter.preFilterCalled);
+ assertEquals(1, CustomLoggingFilter.postFilterCalled);
+ }
+
+ @Test
+ @RunSeparately
+ public void testLoggingFilterTargetClass() {
+ WebTarget target = target().path(App.ROOT_PATH);
+ target.register(CustomLoggingFilter.class).property("foo", "bar");
+ CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+ String s = target.request().get(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+ assertEquals(1, CustomLoggingFilter.preFilterCalled);
+ assertEquals(1, CustomLoggingFilter.postFilterCalled);
+ }
+
+ @Test
+ @RunSeparately
+ public void testLoggingFilterTargetInstance() {
+ WebTarget target = target().path(App.ROOT_PATH);
+ target.register(new CustomLoggingFilter()).property("foo", "bar");
+ CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+ String s = target.request().get(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+ assertEquals(1, CustomLoggingFilter.preFilterCalled);
+ assertEquals(1, CustomLoggingFilter.postFilterCalled);
+ }
+
+ @Test
+ @RunSeparately
+ public void testConfigurationUpdate() {
+ Client client1 = client();
+ client1.register(CustomLoggingFilter.class).property("foo", "bar");
+
+ Client client = ClientBuilder.newClient(client1.getConfiguration());
+ CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+ String s = target().path(App.ROOT_PATH).request().get(String.class);
+ assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+ assertEquals(1, CustomLoggingFilter.preFilterCalled);
+ assertEquals(1, CustomLoggingFilter.postFilterCalled);
+ }
+}
diff --git a/examples/http-patch/README.MD b/examples/http-patch/README.MD
new file mode 100644
index 0000000..9d6f6ad
--- /dev/null
+++ b/examples/http-patch/README.MD
@@ -0,0 +1,101 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Jersey HTTP PATCH
+=================
+
+### *Jersey HTTP PATCH Support Example*
+
+This example demonstrates how to implement generic support for
+[HTTP PATCH Method (RFC-5789)](https://tools.ietf.org/html/rfc5789)
+via JAX-RS reader interceptor. The example has been created based on
+[this Gerard Davison's blogentry] (http://kingsfleet.blogspot.co.uk/2014/02/transparent-patch-support-in-jax-rs-20.html).
+The patch format supported by this example is [JSON Patch (RFC-6902)](http://tools.ietf.org/html/rfc6902).
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+------------------------ | ------------------- | --------------
+**_/patchable-state_** | PatchableResource | GET, PATCH
+
+As you can see in the table, there is only a single resource deployed in
+this application, that supports `GET` and `PATCH` methods. The resource
+represents a *patchable* state, which consists of 3 properties:
+
+- a `title` text property,
+- a `message` text property, and
+- a `list` property that represents a list/array of text strings.
+
+This state can be patch via HTTP GET method by sending the desired
+patch/diff in the JSON Patch format as defined in RFC-6902, for example:
+
+```javascript
+[
+ {
+ "op" : "replace",
+ "path" : "/message",
+ "value" : "patchedMessage"
+ }, {
+ "op" : "add",
+ "path" : "/list/-",
+ "value" : "one"
+ }
+]
+```
+
+This patch will instruct the resource to replace it's `message` property
+content with a new `"patchedMessage"` text and also to append a new
+`"one"` string value to the list of valued contained in the `list`
+property.
+
+(See `HttpPatchTest` for more details.)
+
+Sample Response
+---------------
+
+You can apply a patch using curl:
+
+> curl -v -X PATCH http://localhost:8080/http-patch/patchable-state -H "Content-Type:application/json-patch+json" -d '[{
+> "op": "replace",
+> "path": "/message",
+> "value": "patchedMessage"
+> }, {
+> "op": "add",
+> "path": "/list/-",
+> "value": "one"
+> }]'
+
+The application will answer with a patched object:
+
+```javascript
+{
+ "list" : [ "one" ],
+ "message" : "patchedMessage",
+ "title" : ""
+}
+```
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
+
+A [WADL description](http://wadl.java.net/#spec) may be accessed at the URL:
+
+- <http://localhost:8080/http-patch/application.wadl>
+
+The resource is available at
+
+- <http://localhost:8080/http-patch/patchable-state>
diff --git a/examples/http-patch/pom.xml b/examples/http-patch/pom.xml
new file mode 100644
index 0000000..8bd5179
--- /dev/null
+++ b/examples/http-patch/pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>http-patch</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-http-patch</name>
+
+ <description>
+ Jersey example for implementing generic PATCH support via JAX-RS reader interceptor.
+ Taken from Gerard Davison's blog entry:
+ http://kingsfleet.blogspot.co.uk/2014/02/transparent-patch-support-in-jax-rs-20.html
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.github.fge</groupId>
+ <artifactId>json-patch</artifactId>
+ <version>1.9</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-moxy</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-processing</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.connectors</groupId>
+ <artifactId>jersey-grizzly-connector</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.httppatch.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/App.java b/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/App.java
new file mode 100644
index 0000000..3efc9e6
--- /dev/null
+++ b/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/App.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httppatch;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.ext.ContextResolver;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.moxy.json.MoxyJsonConfig;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * HTTP PATCH Demo Application.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class App {
+ /**
+ * Prevents instantiation.
+ */
+ private App() {
+ }
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/http-patch");
+ /**
+ * Root resource path.
+ */
+ static final String ROOT_PATH = "patchable-state";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("Jersey HTTP PATCH Support Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, create(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.\nTry out %s/%s\nStop the application using CTRL+C",
+ BASE_URI,
+ ROOT_PATH));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Create JAX-RS application for the example.
+ *
+ * @return created application instance.
+ */
+ public static ResourceConfig create() {
+ final ResourceConfig app = new ResourceConfig(
+ PatchableResource.class,
+ OptionsAcceptPatchHeaderFilter.class,
+ PatchingInterceptor.class);
+
+ app.register(createMoxyJsonResolver());
+
+ // Enable on-demand tracing
+ app.property(ServerProperties.TRACING, "ON_DEMAND");
+
+ return app;
+ }
+
+ /**
+ * Create {@link javax.ws.rs.ext.ContextResolver} for {@link org.glassfish.jersey.moxy.json.MoxyJsonConfig}
+ * for this application.
+ *
+ * @return {@code MoxyJsonConfig} context resolver.
+ */
+ public static ContextResolver<MoxyJsonConfig> createMoxyJsonResolver() {
+ final MoxyJsonConfig moxyJsonConfig = new MoxyJsonConfig()
+ .setFormattedOutput(true)
+ .setNamespaceSeparator(':');
+ return moxyJsonConfig.resolver();
+ }
+}
diff --git a/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/OptionsAcceptPatchHeaderFilter.java b/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/OptionsAcceptPatchHeaderFilter.java
new file mode 100644
index 0000000..2715ce0
--- /dev/null
+++ b/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/OptionsAcceptPatchHeaderFilter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httppatch;
+
+import java.io.IOException;
+
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ * A JAX-RS container response filter that applies {@code Accept-Patch} header
+ * to any response to an {@code OPTIONS} request.
+ *
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class OptionsAcceptPatchHeaderFilter implements ContainerResponseFilter {
+
+ private static final String ACCEPT_PATCH_HEADER = "Accept-Patch";
+
+ @Override
+ public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext)
+ throws IOException {
+
+ if (HttpMethod.OPTIONS.equals(requestContext.getMethod())) {
+ final MultivaluedMap<String, Object> headers = responseContext.getHeaders();
+ if (!headers.containsKey(ACCEPT_PATCH_HEADER)) {
+ headers.putSingle(ACCEPT_PATCH_HEADER, MediaType.APPLICATION_JSON_PATCH_JSON);
+ }
+ }
+ }
+}
diff --git a/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/PatchableResource.java b/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/PatchableResource.java
new file mode 100644
index 0000000..06e6440
--- /dev/null
+++ b/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/PatchableResource.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httppatch;
+
+import java.util.logging.Logger;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PATCH;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Patchable resource.
+ *
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path(App.ROOT_PATH)
+@Produces(MediaType.APPLICATION_JSON)
+public class PatchableResource {
+ private static volatile State state = new State();
+ private static final Logger LOGGER = Logger.getLogger(PatchableResource.class.getName());
+
+ /**
+ * Get current resource state.
+ *
+ * @return current resource state.
+ */
+ @GET
+ @Consumes(MediaType.APPLICATION_JSON)
+ public State getState() {
+ return state;
+ }
+
+ /**
+ * Set new resource state.
+ * <p>
+ * We only need to replace the resource state with the one passed into the method
+ * as the actual state patching occurred in the {@link PatchingInterceptor}.
+ * </p>
+ *
+ * @param newState new resource state.
+ * @return patched state.
+ */
+ @PATCH
+ @Consumes(MediaType.APPLICATION_JSON_PATCH_JSON)
+ public State patchState(State newState) {
+ LOGGER.info("New resource state: " + newState.toString());
+ state = newState;
+ return newState;
+ }
+}
diff --git a/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/PatchingInterceptor.java b/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/PatchingInterceptor.java
new file mode 100644
index 0000000..05451b4
--- /dev/null
+++ b/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/PatchingInterceptor.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httppatch;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.ReaderInterceptor;
+import javax.ws.rs.ext.ReaderInterceptorContext;
+
+import org.glassfish.jersey.message.MessageBodyWorkers;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.fge.jsonpatch.JsonPatch;
+import com.github.fge.jsonpatch.JsonPatchException;
+
+/**
+ * JAX-RS reader interceptor that implements server-side PATCH support.
+ *
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class PatchingInterceptor implements ReaderInterceptor {
+
+ private final UriInfo uriInfo;
+ private final MessageBodyWorkers workers;
+
+ /**
+ * {@code PatchingInterceptor} injection constructor.
+ *
+ * @param uriInfo {@code javax.ws.rs.core.UriInfo} proxy instance.
+ * @param workers {@link org.glassfish.jersey.message.MessageBodyWorkers} message body workers.
+ */
+ public PatchingInterceptor(@Context UriInfo uriInfo, @Context MessageBodyWorkers workers) {
+ this.uriInfo = uriInfo;
+ this.workers = workers;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object aroundReadFrom(ReaderInterceptorContext readerInterceptorContext) throws IOException, WebApplicationException {
+ // Get the resource we are being called on, and find the GET method
+ Object resource = uriInfo.getMatchedResources().get(0);
+
+ Method found = null;
+ for (Method next : resource.getClass().getMethods()) {
+ if (next.getAnnotation(GET.class) != null) {
+ found = next;
+ break;
+ }
+ }
+
+ if (found == null) {
+ throw new InternalServerErrorException("No matching GET method on resource");
+ }
+
+ // Invoke the get method to get the state we are trying to patch
+ Object bean;
+ try {
+ bean = found.invoke(resource);
+ } catch (Exception e) {
+ throw new WebApplicationException(e);
+ }
+
+ // Convert this object to a an array of bytes
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ MessageBodyWriter bodyWriter =
+ workers.getMessageBodyWriter(bean.getClass(), bean.getClass(),
+ new Annotation[0], MediaType.APPLICATION_JSON_TYPE);
+
+ bodyWriter.writeTo(bean, bean.getClass(), bean.getClass(),
+ new Annotation[0], MediaType.APPLICATION_JSON_TYPE,
+ new MultivaluedHashMap<String, Object>(), baos);
+
+
+ // Use the Jackson 2.x classes to convert both the incoming patch
+ // and the current state of the object into a JsonNode / JsonPatch
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode serverState = mapper.readValue(baos.toByteArray(), JsonNode.class);
+ JsonNode patchAsNode = mapper.readValue(readerInterceptorContext.getInputStream(), JsonNode.class);
+ JsonPatch patch = JsonPatch.fromJson(patchAsNode);
+
+ try {
+ // Apply the patch
+ JsonNode result = patch.apply(serverState);
+
+ // Stream the result & modify the stream on the readerInterceptor
+ ByteArrayOutputStream resultAsByteArray = new ByteArrayOutputStream();
+ mapper.writeValue(resultAsByteArray, result);
+ readerInterceptorContext.setInputStream(new ByteArrayInputStream(resultAsByteArray.toByteArray()));
+
+ // Pass control back to the Jersey code
+ return readerInterceptorContext.proceed();
+ } catch (JsonPatchException ex) {
+ throw new InternalServerErrorException("Error applying patch.", ex);
+ }
+ }
+}
diff --git a/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/State.java b/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/State.java
new file mode 100644
index 0000000..c00ea15
--- /dev/null
+++ b/examples/http-patch/src/main/java/org/glassfish/jersey/examples/httppatch/State.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httppatch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A resource state modelled as Java bean (that can be patched).
+ *
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class State {
+
+ private String title;
+ private String message;
+ private List<String> list;
+
+ /**
+ * Create new bean instance without any data.
+ */
+ public State() {
+ this.title = "";
+ this.message = "";
+ this.list = new ArrayList<String>();
+ }
+
+ public void setList(List<String> list) {
+ this.list = list;
+ }
+
+ public List<String> getList() {
+ return list;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ State bean = (State) o;
+
+ if (list != null ? !list.equals(bean.list) : bean.list != null) {
+ return false;
+ }
+ if (message != null ? !message.equals(bean.message) : bean.message != null) {
+ return false;
+ }
+ if (title != null ? !title.equals(bean.title) : bean.title != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = title != null ? title.hashCode() : 0;
+ result = 31 * result + (message != null ? message.hashCode() : 0);
+ result = 31 * result + (list != null ? list.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "State{"
+ + "title='" + title + '\''
+ + ", message='" + message + '\''
+ + ", list=" + list
+ + '}';
+ }
+}
diff --git a/examples/http-patch/src/test/java/org/glassfish/jersey/examples/httppatch/HttpPatchTest.java b/examples/http-patch/src/test/java/org/glassfish/jersey/examples/httppatch/HttpPatchTest.java
new file mode 100644
index 0000000..02bbacd
--- /dev/null
+++ b/examples/http-patch/src/test/java/org/glassfish/jersey/examples/httppatch/HttpPatchTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httppatch;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * HTTP PATCH Example unit tests.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class HttpPatchTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ // Uncomment to enable message exchange logging
+ // enable(TestProperties.DUMP_ENTITY);
+ // enable(TestProperties.LOG_TRAFFIC);
+ return App.create();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(App.createMoxyJsonResolver())
+ .connectorProvider(new GrizzlyConnectorProvider());
+
+ }
+
+ /**
+ * This test verifies that the patching of the resource state works.
+ * <p>
+ * The patch is created using the new standard JSON Processing API for Java and
+ * is then sent to the server. {@code PATCH} response as well as the new resource
+ * state obtained via subsequent {@code GET} method is verified against the expected
+ * state.
+ * </p>
+ */
+ @Test
+ public void testPatch() {
+ final WebTarget target = target(App.ROOT_PATH);
+
+ // initial precondition check
+ final State expected = new State();
+ assertEquals(expected, target.request("application/json").get(State.class));
+
+ // apply first patch
+ expected.setMessage("patchedMessage");
+ expected.setTitle("patchedTitle");
+ expected.getList().add("one");
+ expected.getList().add("two");
+
+ JsonArray patch_1 = Json.createArrayBuilder()
+ .add(Json.createObjectBuilder()
+ .add("op", "replace")
+ .add("path", "/message")
+ .add("value", expected.getMessage())
+ .build())
+ .add(Json.createObjectBuilder()
+ .add("op", "replace")
+ .add("path", "/title")
+ .add("value", expected.getTitle())
+ .build())
+ .add(Json.createObjectBuilder()
+ .add("op", "replace")
+ .add("path", "/list")
+ .add("value", Json.createArrayBuilder()
+ .add(expected.getList().get(0))
+ .add(expected.getList().get(1))
+ .build())
+ .build())
+ .build();
+
+ assertEquals(expected, target.request()
+ .method("PATCH",
+ Entity.entity(patch_1, MediaType.APPLICATION_JSON_PATCH_JSON), State.class));
+ assertEquals(expected, target.request("application/json").get(State.class));
+
+ // apply second patch
+ expected.getList().add("three");
+
+ JsonArray patch_2 = Json.createArrayBuilder()
+ .add(Json.createObjectBuilder()
+ .add("op", "add")
+ .add("path", "/list/-")
+ .add("value", expected.getList().get(2))
+ .build())
+ .build();
+ assertEquals(expected, target.request()
+ .method("PATCH",
+ Entity.entity(patch_2, MediaType.APPLICATION_JSON_PATCH_JSON), State.class));
+ assertEquals(expected, target.request("application/json").get(State.class));
+ }
+}
diff --git a/examples/http-trace/README.MD b/examples/http-trace/README.MD
new file mode 100644
index 0000000..e7769b3
--- /dev/null
+++ b/examples/http-trace/README.MD
@@ -0,0 +1,46 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Jersey HTTP PATCH
+=================
+
+### *Jersey HTTP PATCH Support Example*
+
+This example demonstrates how to implement a support for **HTTP TRACE method** via **HttpMethod** annotation in Jersey.
+
+Using HttpMethod annotation you can implement whatever name of an HTTP method you want.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+--------------------------- | ------------------- | --------------
+**_/tracing/annotated_** | TracingResource | TRACE
+**_/tracing/programmatic_** | --- | TRACE
+
+Sample Response
+---------------
+
+You can trace your annotated resource using this command:
+
+> curl -v -X TRACE http://localhost:9998/base/tracing/annotated
+
+or programmatic resource:
+
+> curl -v -X TRACE http://localhost:9998/base/tracing/programmatic
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
\ No newline at end of file
diff --git a/examples/http-trace/pom.xml b/examples/http-trace/pom.xml
new file mode 100644
index 0000000..e0e77ab
--- /dev/null
+++ b/examples/http-trace/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>http-trace</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-http-trace</name>
+
+ <description>Jersey HTTP TRACE support example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.connectors</groupId>
+ <artifactId>jersey-grizzly-connector</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.httptrace.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/http-trace/src/main/java/org/glassfish/jersey/examples/httptrace/App.java b/examples/http-trace/src/main/java/org/glassfish/jersey/examples/httptrace/App.java
new file mode 100644
index 0000000..f2d05af
--- /dev/null
+++ b/examples/http-trace/src/main/java/org/glassfish/jersey/examples/httptrace/App.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httptrace;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.process.Inflector;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.model.Resource;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * This is the example entry point, where Jersey application gets populated and published
+ * using the Grizzly 2 HTTP container.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:9998/base/");
+ /**
+ * Programmatic tracing root resource path.
+ */
+ public static final String ROOT_PATH_PROGRAMMATIC = "tracing/programmatic";
+ /**
+ * Annotated class-based tracing root resource path.
+ */
+ public static final String ROOT_PATH_ANNOTATED = "tracing/annotated";
+
+ /**
+ * Main application entry point.
+ *
+ * @param args application arguments.
+ */
+ public static void main(String[] args) {
+ try {
+ System.out.println("HTTP TRACE Support Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, create(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format(
+ "Application started.\n"
+ + "To test TRACE with a programmatic resource, send HTTP TRACE request to:%n %s%s%n"
+ + "To test TRACE with an annotated resource, send HTTP TRACE request to:%n %s%s%n"
+ + "Stop the application using CTRL+C",
+ BASE_URI, ROOT_PATH_PROGRAMMATIC,
+ BASE_URI, ROOT_PATH_ANNOTATED));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Create example application resource configuration.
+ *
+ * @return initialized resource configuration of the example application.
+ */
+ public static ResourceConfig create() {
+ final ResourceConfig resourceConfig = new ResourceConfig(TracingResource.class);
+
+ final Resource.Builder resourceBuilder = Resource.builder(ROOT_PATH_PROGRAMMATIC);
+ resourceBuilder.addMethod(TRACE.NAME).handledBy(new Inflector<ContainerRequestContext, Response>() {
+
+ @Override
+ public Response apply(ContainerRequestContext request) {
+ if (request == null) {
+ return Response.noContent().build();
+ } else {
+ return Response.ok(Stringifier.stringify((ContainerRequest) request), MediaType.TEXT_PLAIN).build();
+ }
+ }
+ });
+
+ return resourceConfig.registerResources(resourceBuilder.build());
+ }
+}
diff --git a/examples/http-trace/src/main/java/org/glassfish/jersey/examples/httptrace/Stringifier.java b/examples/http-trace/src/main/java/org/glassfish/jersey/examples/httptrace/Stringifier.java
new file mode 100644
index 0000000..1839f1b
--- /dev/null
+++ b/examples/http-trace/src/main/java/org/glassfish/jersey/examples/httptrace/Stringifier.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httptrace;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Request;
+
+import org.glassfish.jersey.server.ContainerRequest;
+
+/**
+ * Request stringifier.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class Stringifier {
+
+ private Stringifier() {
+ }
+
+ public static String stringify(ContainerRequest request) {
+ StringBuilder buffer = new StringBuilder();
+
+ printRequestLine(buffer, request);
+ printPrefixedHeaders(buffer, request.getHeaders());
+
+ if (request.hasEntity()) {
+ buffer.append(request.readEntity(String.class)).append("\n");
+ }
+
+ return buffer.toString();
+ }
+
+ private static void printRequestLine(StringBuilder buffer, ContainerRequest request) {
+ buffer.append(request.getMethod()).append(" ").append(request.getUriInfo().getRequestUri().toASCIIString()).append("\n");
+ }
+
+ private static void printPrefixedHeaders(StringBuilder buffer, Map<String, List<String>> headers) {
+ for (Map.Entry<String, List<String>> e : headers.entrySet()) {
+ List<String> val = e.getValue();
+ String header = e.getKey();
+
+ if (val.size() == 1) {
+ buffer.append(header).append(": ").append(val.get(0)).append("\n");
+ } else {
+ StringBuilder sb = new StringBuilder();
+ boolean add = false;
+ for (String s : val) {
+ if (add) {
+ sb.append(',');
+ }
+ add = true;
+ sb.append(s);
+ }
+ buffer.append(header).append(": ").append(sb.toString()).append("\n");
+ }
+ }
+ }
+}
diff --git a/examples/http-trace/src/main/java/org/glassfish/jersey/examples/httptrace/TRACE.java b/examples/http-trace/src/main/java/org/glassfish/jersey/examples/httptrace/TRACE.java
new file mode 100644
index 0000000..e37d522
--- /dev/null
+++ b/examples/http-trace/src/main/java/org/glassfish/jersey/examples/httptrace/TRACE.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httptrace;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import javax.ws.rs.HttpMethod;
+
+/**
+ * HTTP TRACE method annotation.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@HttpMethod(TRACE.NAME)
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TRACE {
+ public static final String NAME = "TRACE";
+}
diff --git a/examples/http-trace/src/main/java/org/glassfish/jersey/examples/httptrace/TracingResource.java b/examples/http-trace/src/main/java/org/glassfish/jersey/examples/httptrace/TracingResource.java
new file mode 100644
index 0000000..d6fd371
--- /dev/null
+++ b/examples/http-trace/src/main/java/org/glassfish/jersey/examples/httptrace/TracingResource.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httptrace;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Request;
+
+import org.glassfish.jersey.server.ContainerRequest;
+
+/**
+ * This very basic resource showcases support of a HTTP TRACE method,
+ * not directly supported by JAX-RS API.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path(App.ROOT_PATH_ANNOTATED)
+public class TracingResource {
+
+ @TRACE
+ @Produces("text/plain")
+ public String trace(Request request) {
+ return Stringifier.stringify((ContainerRequest) request);
+ }
+}
diff --git a/examples/http-trace/src/test/java/org/glassfish/jersey/examples/httptrace/TraceSupportTest.java b/examples/http-trace/src/test/java/org/glassfish/jersey/examples/httptrace/TraceSupportTest.java
new file mode 100644
index 0000000..4dec416
--- /dev/null
+++ b/examples/http-trace/src/test/java/org/glassfish/jersey/examples/httptrace/TraceSupportTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httptrace;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class TraceSupportTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.DUMP_ENTITY);
+ enable(TestProperties.LOG_TRAFFIC);
+ return App.create();
+ }
+
+ private String[] expectedFragmentsProgrammatic = new String[]{
+ "TRACE http://localhost:" + this.getPort() + "/tracing/programmatic"
+ };
+ private String[] expectedFragmentsAnnotated = new String[]{
+ "TRACE http://localhost:" + this.getPort() + "/tracing/annotated"
+ };
+
+ private WebTarget prepareTarget(String path) {
+ final WebTarget target = target();
+ target.register(LoggingFeature.class);
+ return target.path(path);
+ }
+
+ @Test
+ public void testProgrammaticApp() throws Exception {
+ Response response = prepareTarget(App.ROOT_PATH_PROGRAMMATIC).request("text/plain").method(TRACE.NAME);
+
+ assertEquals(Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode());
+
+ String responseEntity = response.readEntity(String.class);
+ for (String expectedFragment : expectedFragmentsProgrammatic) {
+ assertTrue("Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity,
+ // toLowerCase - http header field names are case insensitive
+ responseEntity.contains(expectedFragment));
+ }
+ }
+
+ @Test
+ public void testAnnotatedApp() throws Exception {
+ Response response = prepareTarget(App.ROOT_PATH_ANNOTATED).request("text/plain").method(TRACE.NAME);
+
+ assertEquals(Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode());
+
+ String responseEntity = response.readEntity(String.class);
+ for (String expectedFragment : expectedFragmentsAnnotated) {
+ assertTrue("Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity,
+ // toLowerCase - http header field names are case insensitive
+ responseEntity.contains(expectedFragment));
+ }
+ }
+
+ @Test
+ public void testTraceWithEntity() throws Exception {
+ _testTraceWithEntity(false, false);
+ }
+
+ @Test
+ public void testAsyncTraceWithEntity() throws Exception {
+ _testTraceWithEntity(true, false);
+ }
+
+ @Test
+ public void testTraceWithEntityGrizzlyConnector() throws Exception {
+ _testTraceWithEntity(false, true);
+ }
+
+ @Test
+ public void testAsyncTraceWithEntityGrizzlyConnector() throws Exception {
+ _testTraceWithEntity(true, true);
+ }
+
+ private void _testTraceWithEntity(final boolean isAsync, final boolean useGrizzlyConnection) throws Exception {
+ try {
+ WebTarget target = useGrizzlyConnection ? getGrizzlyClient().target(target().getUri()) : target();
+ target = target.path(App.ROOT_PATH_ANNOTATED);
+
+ final Entity<String> entity = Entity.entity("trace", MediaType.WILDCARD_TYPE);
+
+ Response response;
+ if (!isAsync) {
+ response = target.request().method(TRACE.NAME, entity);
+ } else {
+ response = target.request().async().method(TRACE.NAME, entity).get();
+ }
+
+ fail("A TRACE request MUST NOT include an entity. (response=" + response + ")");
+ } catch (Exception e) {
+ // OK
+ }
+ }
+
+ private Client getGrizzlyClient() {
+ return ClientBuilder.newClient(new ClientConfig().connectorProvider(new GrizzlyConnectorProvider()));
+ }
+}
diff --git a/examples/https-clientserver-grizzly/README.MD b/examples/https-clientserver-grizzly/README.MD
new file mode 100644
index 0000000..7a6180d
--- /dev/null
+++ b/examples/https-clientserver-grizzly/README.MD
@@ -0,0 +1,114 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+HTTPS Client/Server Example - Grizzly
+=====================================
+
+This example demonstrates how to develop RESTful HTTPS Server using
+Grizzly and how to implement HTTPS Client using Jersey with server
+authentication.
+
+Contents
+--------
+
+### Server side
+
+This example consists of just one resource - RootResource, which is
+basically copy of HelloWorld Resource from corresponding sample with
+little improvement.
+
+Other classes are used to start Grizzly embedded server and set up its
+authentication and authorization mechanism and keystore and truststore.
+
+### Client side
+
+Client side is implemented as a test case, see class
+**org.glassfish.jersey.examples.httpsclientservergrizzly.MainTest**, and
+its method `testSSLWithAuth` (others are just tests for invalid
+authorization). First thing you have to do if you want to communicate
+with service via https is set up **SSLContext** which is basically
+providing keystore and truststore. Keystore is used for storing own keys
+and truststore is used for storing certificates to which you have
+decided to trust. For more informations see [\[1\]](#JSSERefGuide).
+
+To set **SSLContext** on Jersey client you have to set it as a property
+to the client instance:
+
+ Client client = ClientFactory.newClient();
+ client.configuration().setProperty(ClientProperties.SSL_CONTEXT, context);
+
+### Certificates setup
+
+**These steps are not required to run this example. Pre-generated
+keystore and truststore files are already present.**
+
+We needed set up few things to get this example working:
+
+- generate client and server keys
+- generate client and server certificates
+- import certificates to corresponding truststores
+
+Client certificate is needed too because we're going to use server-side
+certificate authentication as well (yes, after this Http Basic
+authentication seems to be kind of redundant but there are some usecases
+where you might want to use them both).
+Generate client key and store it into keystore:\
+
+ keytool -genkey -keystore ./keystore_client -alias clientKey -dname "CN=Client, OU=Jersey, O=Oracle Corporation, L=Prague, ST=Czech Republic, C=CZ"
+
+Generate client certificate (this will generate self-signed certificate;
+if you have certification authority and want generate certificate
+request, use keytool -certreq):
+
+ keytool -export -alias clientKey -rfc -keystore ./keystore_client > ./client.cert
+
+Import client certificate to servers truststore:
+
+ keytool -import -alias clientCert -file ./client.cert -keystore ./truststore_server
+
+These steps are similar for server side:
+
+ keytool -genkey -keystore ./keystore_server -alias serverKey -dname "CN=localhost, OU=Jersey, O=Oracle Corporation, L=Prague, ST=Czech Republic, C=CZ"
+ keytool -export -alias serverKey -rfc -keystore ./keystore_server > ./server.cert
+ keytool -import -alias serverCert -file ./server.cert -keystore ./truststore_client
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+test
+
+> mvn clean test
+
+run
+
+> mvn compile exec:java
+
+From a web browser, visit:\
+ **This won't work! \***
+
+> `https://localhost:8463`
+
+**\[\*\]** Your web browser needs have and use generated client keys. Or
+you have to disable server side client authentication - set NeedClientAuth to false:
+**new SSLEngineConfigurator(sslContext).setClientMode(false).setNeedClientAuth(false)** in *Server.java*.
+
+Then ignore any security warning (self-signed certificates aren't trusted in general)
+and login with username "user" and password "password". Text "JERSEY
+HTTPS EXAMPLE" should appear.
+
+Mozila Firefox and Internet Explorer don't allow users to display any
+content provided on behalf of any self-signed certificate so you have to
+use some other browser which allows this (for example Safari or Opera).
+
+References
+----------
+
+\[1\]
+<http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html>
diff --git a/examples/https-clientserver-grizzly/client.cert b/examples/https-clientserver-grizzly/client.cert
new file mode 100644
index 0000000..d43d88b
--- /dev/null
+++ b/examples/https-clientserver-grizzly/client.cert
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIIDJTCCAuOgAwIBAgIET5ZyYjALBgcqhkjOOAQDBQAwdjELMAkGA1UEBhMCQ1oxFzAVBgNVBAgT
+DkN6ZWNoIFJlcHVibGljMQ8wDQYDVQQHEwZQcmFndWUxGzAZBgNVBAoTEk9yYWNsZSBDb3Jwb3Jh
+dGlvbjEPMA0GA1UECxMGSmVyc2V5MQ8wDQYDVQQDEwZDbGllbnQwHhcNMTIwNDI0MDkyOTA2WhcN
+MTIwNzIzMDkyOTA2WjB2MQswCQYDVQQGEwJDWjEXMBUGA1UECBMOQ3plY2ggUmVwdWJsaWMxDzAN
+BgNVBAcTBlByYWd1ZTEbMBkGA1UEChMST3JhY2xlIENvcnBvcmF0aW9uMQ8wDQYDVQQLEwZKZXJz
+ZXkxDzANBgNVBAMTBkNsaWVudDCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu
+7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSf
+n+gEexAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgB
+xwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlXTAs9
+B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6
+ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQBTDv+z0kqA4GEAAKBgBmHNACDk1aw
+vUZjsRecMSBlkkCSqr/cCrYOsNwpfleQKsM6rdOofujANUVeoUFhX8e8K45FknxEqAugmhGQ9NRn
+uMenrvV+XupC0V2uGH0OciXeAzHbfeItBCbmJcvMdPW/q+I2vFchv6+ajEiNHogBrCc3qwSMhyVQ
+ug2fXHmJMAsGByqGSM44BAMFAAMvADAsAhQYznYmH0hrcLni4EqX3Ovac+pNJgIUehnEaW1V5djn
+dhYBAYUkSycETl4=
+-----END CERTIFICATE-----
diff --git a/examples/https-clientserver-grizzly/keystore_client b/examples/https-clientserver-grizzly/keystore_client
new file mode 100644
index 0000000..d016fd2
--- /dev/null
+++ b/examples/https-clientserver-grizzly/keystore_client
Binary files differ
diff --git a/examples/https-clientserver-grizzly/keystore_server b/examples/https-clientserver-grizzly/keystore_server
new file mode 100644
index 0000000..a7c93fc
--- /dev/null
+++ b/examples/https-clientserver-grizzly/keystore_server
Binary files differ
diff --git a/examples/https-clientserver-grizzly/pom.xml b/examples/https-clientserver-grizzly/pom.xml
new file mode 100644
index 0000000..fb060b0
--- /dev/null
+++ b/examples/https-clientserver-grizzly/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>https-clientserver-grizzly</artifactId>
+ <name>jersey-examples-https-clientserver-grizzly</name>
+ <packaging>jar</packaging>
+
+ <description>Jersey HTTPS Client/Server example on Grizzly.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-servlet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.connectors</groupId>
+ <artifactId>jersey-grizzly-connector</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.httpsclientservergrizzly.Server</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/https-clientserver-grizzly/server.cert b/examples/https-clientserver-grizzly/server.cert
new file mode 100644
index 0000000..820841c
--- /dev/null
+++ b/examples/https-clientserver-grizzly/server.cert
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIIDKzCCAumgAwIBAgIET5ZyzjALBgcqhkjOOAQDBQAweTELMAkGA1UEBhMCQ1oxFzAVBgNVBAgT
+DkN6ZWNoIFJlcHVibGljMQ8wDQYDVQQHEwZQcmFndWUxGzAZBgNVBAoTEk9yYWNsZSBDb3Jwb3Jh
+dGlvbjEPMA0GA1UECxMGSmVyc2V5MRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTIwNDI0MDkzMDU0
+WhcNMTIwNzIzMDkzMDU0WjB5MQswCQYDVQQGEwJDWjEXMBUGA1UECBMOQ3plY2ggUmVwdWJsaWMx
+DzANBgNVBAcTBlByYWd1ZTEbMBkGA1UEChMST3JhY2xlIENvcnBvcmF0aW9uMQ8wDQYDVQQLEwZK
+ZXJzZXkxEjAQBgNVBAMTCWxvY2FsaG9zdDCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUS
+KVLfSpwu7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3
+a5lQpaSfn+gEexAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/Ii
+Axmd0UgBxwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrq
+gvlXTAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCjrh4rs6Z1
+kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQBTDv+z0kqA4GEAAKBgGIs
+VTo7dODp6iOyHFL+mkOVlOloZcymyWlHUZXzKqrvAi5jISptZZM+AoJcUUlUWEO9uwVTvX0MCk+4
+viwlPwt+XhaPM0kqfFcx1IS07BAx7z9cXREfYQpoFSsFW7pUs6cdvu0rjj8Ip6BnHALxQDgaBk40
+zXM39kB9LdGBt4uDMAsGByqGSM44BAMFAAMvADAsAhQE4QoQP4xPibjnozo8x5ORJqBuCAIUTkLQ
+2udZ2DeknwPYXp/zMkYXLN4=
+-----END CERTIFICATE-----
diff --git a/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/AuthenticationException.java b/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/AuthenticationException.java
new file mode 100644
index 0000000..73eeedf
--- /dev/null
+++ b/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/AuthenticationException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httpsclientservergrizzly;
+
+/**
+ * A runtime exception representing a failure to provide correct authentication credentials.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class AuthenticationException extends RuntimeException {
+
+ public AuthenticationException(String message, String realm) {
+ super(message);
+ this.realm = realm;
+ }
+
+ private String realm = null;
+
+ public String getRealm() {
+ return this.realm;
+ }
+
+}
diff --git a/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/AuthenticationExceptionMapper.java b/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/AuthenticationExceptionMapper.java
new file mode 100644
index 0000000..78d12f7
--- /dev/null
+++ b/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/AuthenticationExceptionMapper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httpsclientservergrizzly;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Map an authentication exception to an HTTP 401 response, optionally including the realm for a credentials challenge at the client.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Provider
+public class AuthenticationExceptionMapper implements ExceptionMapper<AuthenticationException> {
+
+ public Response toResponse(AuthenticationException e) {
+ if (e.getRealm() != null) {
+ return Response
+ .status(Status.UNAUTHORIZED)
+ .header("WWW-Authenticate", "Basic realm=\"" + e.getRealm() + "\"")
+ .type("text/plain")
+ .entity(e.getMessage())
+ .build();
+ } else {
+ return Response
+ .status(Status.UNAUTHORIZED)
+ .type("text/plain")
+ .entity(e.getMessage())
+ .build();
+ }
+ }
+
+}
diff --git a/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/RootResource.java b/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/RootResource.java
new file mode 100644
index 0000000..1385d2f
--- /dev/null
+++ b/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/RootResource.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httpsclientservergrizzly;
+
+import java.nio.charset.Charset;
+import javax.xml.bind.DatatypeConverter;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+
+/**
+ * Simple resource demonstrating low level approach of getting user credentials.
+ *
+ * Better way would be injecting {@link javax.ws.rs.core.SecurityContext}.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Path("/")
+public class RootResource {
+
+ @GET
+ public String get1(@Context HttpHeaders headers) {
+ // you can get username form HttpHeaders
+ System.out.println("Service: GET / User: " + getUser(headers));
+
+ return Server.CONTENT;
+ }
+
+ private String getUser(HttpHeaders headers) {
+
+ // this is a very minimalistic and "naive" code; if you plan to use it
+ // add necessary checks (see org.glassfish.jersey.examples.httpsclientservergrizzly.authservergrizzly.SecurityFilter)
+
+ String auth = headers.getRequestHeader("authorization").get(0);
+
+ auth = auth.substring("Basic ".length());
+ String[] values = new String(DatatypeConverter.parseBase64Binary(auth), Charset.forName("ASCII")).split(":");
+
+ // String username = values[0];
+ // String password = values[1];
+
+ return values[0];
+ }
+}
diff --git a/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/SecurityFilter.java b/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/SecurityFilter.java
new file mode 100644
index 0000000..5918712
--- /dev/null
+++ b/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/SecurityFilter.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httpsclientservergrizzly;
+
+import java.io.IOException;
+import java.security.Principal;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.PreMatching;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.Provider;
+
+import javax.inject.Inject;
+
+import java.nio.charset.Charset;
+import javax.xml.bind.DatatypeConverter;
+
+/**
+ * Simple authentication filter.
+ *
+ * Returns response with http status 401 when proper authentication is not provided in incoming request.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @see ContainerRequestFilter
+ */
+@Provider
+@PreMatching
+public class SecurityFilter implements ContainerRequestFilter {
+
+ @Inject
+ javax.inject.Provider<UriInfo> uriInfo;
+ private static final String REALM = "HTTPS Example authentication";
+
+ @Override
+ public void filter(ContainerRequestContext filterContext) throws IOException {
+ User user = authenticate(filterContext);
+ filterContext.setSecurityContext(new Authorizer(user));
+ }
+
+ private User authenticate(ContainerRequestContext filterContext) {
+ // Extract authentication credentials
+ String authentication = filterContext.getHeaderString(HttpHeaders.AUTHORIZATION);
+ if (authentication == null) {
+ throw new AuthenticationException("Authentication credentials are required", REALM);
+ }
+ if (!authentication.startsWith("Basic ")) {
+ return null;
+ // additional checks should be done here
+ // "Only HTTP Basic authentication is supported"
+ }
+ authentication = authentication.substring("Basic ".length());
+ String[] values = new String(DatatypeConverter.parseBase64Binary(authentication), Charset.forName("ASCII")).split(":");
+ if (values.length < 2) {
+ throw new WebApplicationException(400);
+ // "Invalid syntax for username and password"
+ }
+ String username = values[0];
+ String password = values[1];
+ if ((username == null) || (password == null)) {
+ throw new WebApplicationException(400);
+ // "Missing username or password"
+ }
+
+ // Validate the extracted credentials
+ User user;
+
+ if (username.equals("user") && password.equals("password")) {
+ user = new User("user", "user");
+ System.out.println("USER AUTHENTICATED");
+ // } else if (username.equals("admin") && password.equals("adminadmin")) {
+ // user = new User("admin", "admin");
+ // System.out.println("ADMIN AUTHENTICATED");
+ } else {
+ System.out.println("USER NOT AUTHENTICATED");
+ throw new AuthenticationException("Invalid username or password\r\n", REALM);
+ }
+ return user;
+ }
+
+ public class Authorizer implements SecurityContext {
+
+ private User user;
+ private Principal principal;
+
+ public Authorizer(final User user) {
+ this.user = user;
+ this.principal = new Principal() {
+
+ public String getName() {
+ return user.username;
+ }
+ };
+ }
+
+ public Principal getUserPrincipal() {
+ return this.principal;
+ }
+
+ public boolean isUserInRole(String role) {
+ return (role.equals(user.role));
+ }
+
+ public boolean isSecure() {
+ return "https".equals(uriInfo.get().getRequestUri().getScheme());
+ }
+
+ public String getAuthenticationScheme() {
+ return SecurityContext.BASIC_AUTH;
+ }
+ }
+
+ public class User {
+
+ public String username;
+ public String role;
+
+ public User(String username, String role) {
+ this.username = username;
+ this.role = role;
+ }
+ }
+}
diff --git a/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/Server.java b/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/Server.java
new file mode 100644
index 0000000..b4c91e4
--- /dev/null
+++ b/examples/https-clientserver-grizzly/src/main/java/org/glassfish/jersey/examples/httpsclientservergrizzly/Server.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httpsclientservergrizzly;
+
+import java.io.IOException;
+import java.net.URI;
+import java.security.AccessController;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.grizzly.ssl.SSLContextConfigurator;
+import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
+
+/**
+ * A simple SSL-secured HTTP server.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class Server {
+
+ private static final Logger LOGGER = Logger.getLogger(Server.class.getName());
+
+ private static final String KEYSTORE_SERVER_FILE = "./keystore_server";
+ private static final String KEYSTORE_SERVER_PWD = "asdfgh";
+ private static final String TRUSTORE_SERVER_FILE = "./truststore_server";
+ private static final String TRUSTORE_SERVER_PWD = "asdfgh";
+
+ public static final URI BASE_URI = getBaseURI();
+ public static final String CONTENT = "JERSEY HTTPS EXAMPLE\n";
+
+ private final HttpServer webServer;
+
+ private Server(final HttpServer webServer) {
+ this.webServer = webServer;
+ }
+
+ private static URI getBaseURI() {
+ return UriBuilder.fromUri("https://localhost/").port(getPort(8463)).build();
+ }
+
+ private static int getPort(int defaultPort) {
+ final String port =
+ AccessController.doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+ if (null != port) {
+ try {
+ return Integer.parseInt(port);
+ } catch (NumberFormatException e) {
+ LOGGER.warning("Value of jersey.config.test.container.port property"
+ + " is not a valid positive integer [" + port + "]."
+ + " Reverting to default [" + defaultPort + "].");
+ }
+ }
+ return defaultPort;
+ }
+
+ /**
+ * Start SSL-secured HTTP test server.
+ *
+ * @throws IOException in case there is an error while reading server key store or trust store.
+ * @return an instance of the started SSL-secured HTTP test server.
+ */
+ public static Server start() throws IOException {
+ // Grizzly ssl configuration
+ SSLContextConfigurator sslContext = new SSLContextConfigurator();
+
+ // set up security context
+ sslContext.setKeyStoreFile(KEYSTORE_SERVER_FILE); // contains server keypair
+ sslContext.setKeyStorePass(KEYSTORE_SERVER_PWD);
+ sslContext.setTrustStoreFile(TRUSTORE_SERVER_FILE); // contains client certificate
+ sslContext.setTrustStorePass(TRUSTORE_SERVER_PWD);
+
+ ResourceConfig rc = new ResourceConfig();
+ rc.registerClasses(RootResource.class, SecurityFilter.class, AuthenticationExceptionMapper.class);
+
+ final HttpServer grizzlyServer = GrizzlyHttpServerFactory.createHttpServer(
+ getBaseURI(),
+ rc,
+ true,
+ new SSLEngineConfigurator(sslContext).setClientMode(false).setNeedClientAuth(true)
+ );
+
+ // start Grizzly embedded server //
+ LOGGER.info("Jersey app started. Try out " + BASE_URI + "\nHit CTRL + C to stop it...");
+ grizzlyServer.start();
+
+ return new Server(grizzlyServer);
+ }
+
+ /**
+ * Stop SSL-secured HTTP test server.
+ */
+ public void stop() {
+ webServer.shutdownNow();
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public static void main(String[] args) throws InterruptedException, IOException {
+ start();
+
+ System.in.read();
+ }
+}
diff --git a/examples/https-clientserver-grizzly/src/test/java/org/glassfish/jersey/examples/httpsclientservergrizzly/MainTest.java b/examples/https-clientserver-grizzly/src/test/java/org/glassfish/jersey/examples/httpsclientservergrizzly/MainTest.java
new file mode 100644
index 0000000..97fefa8
--- /dev/null
+++ b/examples/https-clientserver-grizzly/src/test/java/org/glassfish/jersey/examples/httpsclientservergrizzly/MainTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.httpsclientservergrizzly;
+
+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.net.ssl.SSLContext;
+
+import org.glassfish.jersey.SslConfigurator;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.HttpUrlConnectorProvider;
+import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
+import org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider;
+import org.glassfish.jersey.logging.LoggingFeature;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * This test class starts the grizzly server and then client performs several SSL (https)
+ * requests where different scenarios are tested (SSL Client authentication, missing truststore
+ * configuration, etc.). Server is a Grizzly server configured for SSL support and client
+ * uses both, {@link HttpUrlConnectorProvider} and {@link GrizzlyConnectorProvider}.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class MainTest {
+
+ private static final String TRUSTORE_CLIENT_FILE = "./truststore_client";
+ private static final String TRUSTSTORE_CLIENT_PWD = "asdfgh";
+ private static final String KEYSTORE_CLIENT_FILE = "./keystore_client";
+ private static final String KEYSTORE_CLIENT_PWD = "asdfgh";
+
+ private final Object serverGuard = new Object();
+ private Server server = null;
+
+ @Before
+ public void setUp() throws Exception {
+ synchronized (serverGuard) {
+ if (server != null) {
+ throw new IllegalStateException(
+ "Test run sync issue: Another instance of the SSL-secured HTTP test server has been already started.");
+ }
+ server = Server.start();
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ synchronized (serverGuard) {
+ if (server == null) {
+ throw new IllegalStateException("Test run sync issue: There is no SSL-secured HTTP test server to stop.");
+ }
+ server.stop();
+ server = null;
+ }
+ }
+
+ @Test
+ public void testSSLWithBasicAndSSLAuthGrizzlyConnector() {
+ final ClientConfig clientConfig = getGrizzlyConfig();
+ _testSSLWithBasicAndSSLAuth(clientConfig);
+ }
+
+ private ClientConfig getGrizzlyConfig() {
+ return new ClientConfig().connectorProvider(new GrizzlyConnectorProvider());
+ }
+
+ @Test
+ public void testSSLWithBasicAndSSLAuthHttpUrlConnector() {
+ final ClientConfig clientConfig = getHttpUrlConnectorConfig();
+ _testSSLWithBasicAndSSLAuth(clientConfig);
+ }
+
+ private ClientConfig getHttpUrlConnectorConfig() {
+ return new ClientConfig().connectorProvider(new HttpUrlConnectorProvider());
+ }
+
+ /**
+ * Test to see that the correct Http status is returned.
+ */
+ private void _testSSLWithBasicAndSSLAuth(ClientConfig clientConfig) {
+ SslConfigurator sslConfig = SslConfigurator.newInstance()
+ .trustStoreFile(TRUSTORE_CLIENT_FILE)
+ .trustStorePassword(TRUSTSTORE_CLIENT_PWD)
+ .keyStoreFile(KEYSTORE_CLIENT_FILE)
+ .keyPassword(KEYSTORE_CLIENT_PWD);
+
+ final SSLContext sslContext = sslConfig.createSSLContext();
+ Client client = ClientBuilder.newBuilder().withConfig(clientConfig)
+ .sslContext(sslContext).build();
+
+ // client basic auth demonstration
+ client.register(HttpAuthenticationFeature.basic("user", "password"));
+
+ System.out.println("Client: GET " + Server.BASE_URI);
+
+ WebTarget target = client.target(Server.BASE_URI);
+
+ final Response response = target.path("/").request().get(Response.class);
+
+ assertEquals(200, response.getStatus());
+ }
+
+
+ @Test
+ public void testWithoutBasicAuthHttpUrlConnector() {
+ _testWithoutBasicAuth(getHttpUrlConnectorConfig());
+ }
+
+ @Test
+ public void testWithoutBasicAuthGrizzlyConnector() {
+ _testWithoutBasicAuth(getGrizzlyConfig());
+ }
+
+ /**
+ * Test to see that HTTP 401 is returned when client tries to GET without
+ * proper credentials.
+ */
+ private void _testWithoutBasicAuth(ClientConfig clientConfig) {
+ SslConfigurator sslConfig = SslConfigurator.newInstance()
+ .trustStoreFile(TRUSTORE_CLIENT_FILE)
+ .trustStorePassword(TRUSTSTORE_CLIENT_PWD)
+ .keyStoreFile(KEYSTORE_CLIENT_FILE)
+ .keyPassword(KEYSTORE_CLIENT_PWD);
+
+ Client client = ClientBuilder.newBuilder().withConfig(clientConfig).sslContext(sslConfig
+ .createSSLContext()).build();
+
+ System.out.println("Client: GET " + Server.BASE_URI);
+
+ WebTarget target = client.target(Server.BASE_URI);
+ target.register(LoggingFeature.class);
+
+ Response response;
+
+ try {
+ response = target.path("/").request().get(Response.class);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+
+ assertEquals(401, response.getStatus());
+ }
+
+ /**
+ * Test to see that SSLHandshakeException is thrown when client don't have
+ * trusted key.
+ */
+ private void _testWithoutSSLAuthentication(ClientConfig clientConfig) {
+ SslConfigurator sslConfig = SslConfigurator.newInstance()
+ .trustStoreFile(TRUSTORE_CLIENT_FILE)
+ .trustStorePassword(TRUSTSTORE_CLIENT_PWD);
+
+ Client client = ClientBuilder.newBuilder()
+ .withConfig(clientConfig)
+ .sslContext(sslConfig.createSSLContext()).build();
+
+ System.out.println("Client: GET " + Server.BASE_URI);
+
+ WebTarget target = client.target(Server.BASE_URI);
+ target.register(LoggingFeature.class);
+
+ boolean caught = false;
+
+ try {
+ target.path("/").request().get(String.class);
+ } catch (Exception e) {
+ caught = true;
+ }
+
+ assertTrue(caught);
+ // solaris throws java.net.SocketException instead of SSLHandshakeException
+ // assertTrue(msg.contains("SSLHandshakeException"));
+ }
+
+ @Test
+ public void testWithoutSSLAuthenticationGrizzly() {
+ _testWithoutSSLAuthentication(getGrizzlyConfig());
+ }
+
+ @Test
+ public void testWithoutSSLAuthenticationHttpUrlConnector() {
+ _testWithoutSSLAuthentication(getHttpUrlConnectorConfig());
+ }
+}
diff --git a/examples/https-clientserver-grizzly/truststore_client b/examples/https-clientserver-grizzly/truststore_client
new file mode 100644
index 0000000..74784fb
--- /dev/null
+++ b/examples/https-clientserver-grizzly/truststore_client
Binary files differ
diff --git a/examples/https-clientserver-grizzly/truststore_server b/examples/https-clientserver-grizzly/truststore_server
new file mode 100644
index 0000000..9b26ce4
--- /dev/null
+++ b/examples/https-clientserver-grizzly/truststore_server
Binary files differ
diff --git a/examples/https-server-glassfish/README.MD b/examples/https-server-glassfish/README.MD
new file mode 100644
index 0000000..4b7ad35
--- /dev/null
+++ b/examples/https-server-glassfish/README.MD
@@ -0,0 +1,101 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+HTTPS Server Example - GlassFish
+================================
+
+This example demonstrates how to develop secure RESTful "Hello world"
+web service with a Servlet 2.5 container.
+
+Contents
+--------
+
+The example consists of just one Java class:
+
+`org.glassfish.jersey.examples.https.glassfish.resources.HelloWorldResource`
+
+A resource class that produces a textual response to an HTTP GET request.
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+------------------- | -------------------- | --------------
+**_/helloworld_** | HelloWorldResource | GET
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+Create war package
+
+> mvn clean package
+
+Setup GlassFish 4.0 (installed at `AS_HOME=<install_dir>/glassfish4`):
+
+Add new security realm called "`myRealm`"
+- Start GlassFish using asadmin `$AS_HOME/bin/asadmin start-domain`
+- Access Admin console, assuming the use of default admin port, at
+ <http://localhost:4848>
+- Navigate in Admin console to:
+ `Configurations -> server-config -> Security -> Realms`, click on
+ "`New...`"
+- Create user and set password for user `myUser` (click on "`myRealm`"
+ -> "`Manage Users`" -> "`New...`");
+ - **Ensure** "`User ID:`" set to "`myUser`" **and**
+ "`Group List:`" set to "`Users`"
+ - Enter password **`myPass`** in "`New Password:`" and
+ "`Confirm New Password:`" text fields
+- Add one other user name `joeUser` (click on "`myRealm`" ->
+ "`Manage Users`" -> "`New...`");
+ - **Ensure** "`User ID:`" set to "`joeUser`" **and**
+ "`Group List:`" set to "`Users`"
+ - Enter password **`joePass`** in "`New Password:`" and
+ "`Confirm New Password:`" text fields
+
+Check `web.xml` and `sun-web.xml` to understand how role mappings are
+configured. [This
+blog](http://blogs.oracle.com/bobby/entry/simplified_security_role_mapping)
+is also helpful in helping to clarify role mappings.
+Deploy `https-server-glassfish.war` as a Web Application
+- Admin console: select `Applications`, click on "`Deploy...`". Select
+ file `./target/https-server-glassfish.war`
+- or you can use command line:
+ `$AS_HOME/bin/asadmin deploy ./target/https-server-glassfish.war`
+
+From a web browser, visit:
+
+- <https://localhost:8181/https-server-glassfish-webapp/helloworld>
+- Enter either Username `myUser` and Password `myPass` or, Username `joeUser` and Password `joePass`
+- If entered Username `myUser` and Password `myPass`, verify that
+ **expected output**: `Sending "Hello World" to user "myUser"` was displayed
+- If entered Username `joeUser` and Password `joePass`, verify that
+ **expected output**: `Sending "Hello World" to user "joeUser"` was displayed
+
+Running the Negative Example
+----------------------------
+
+Run the example as follows:
+
+1. Create war package
+
+ > mvn clean package
+
+2. setup GlassFish 4.0 (installed at
+ `AS_HOME=<install_dir>/glassfish4`):
+
+ - Follow the steps described in the "**setup GlassFish 3.1**"
+ section from the previous chapter "**Running the Example**" but
+ instead of adding the user "`joeUser`" to the "`Group List:`" of
+ "`Users`" set "`Group List:`" for this user to "`OtherUsers`"
+
+3. From a web browser, visit:
+
+ - <https://localhost:8181/https-server-glassfish-webapp/helloworld>
+ - Enter Username `joeUser` and Password `joePass`, verify that **expected output**
+ looks like: `HTTP Status 403 - Access to the requested resource has been denied`
diff --git a/examples/https-server-glassfish/pom.xml b/examples/https-server-glassfish/pom.xml
new file mode 100644
index 0000000..844ba0d
--- /dev/null
+++ b/examples/https-server-glassfish/pom.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>https-server-glassfish</artifactId>
+ <name>jersey-examples-https-server-glassfish</name>
+ <packaging>war</packaging>
+
+ <description>Jersey HTTPS server on GlassFish example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-servlet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Uncomment to run the application using "mvn embedded-glassfish:run"
+ (does not work with the security config required by example at the moment) -->
+ <!--plugin>
+ <groupId>org.glassfish.embedded</groupId>
+ <artifactId>maven-embedded-glassfish-plugin</artifactId>
+ </plugin-->
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/https-server-glassfish/src/main/java/org/glassfish/jersey/examples/https/glassfish/resources/HelloWorldResource.java b/examples/https-server-glassfish/src/main/java/org/glassfish/jersey/examples/https/glassfish/resources/HelloWorldResource.java
new file mode 100644
index 0000000..8d4dc98
--- /dev/null
+++ b/examples/https-server-glassfish/src/main/java/org/glassfish/jersey/examples/https/glassfish/resources/HelloWorldResource.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.https.glassfish.resources;
+
+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.Response;
+
+import javax.annotation.security.RolesAllowed;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * The JAX-RS resource class will be hosted at the URI path {@code "/helloworld"}.
+ *
+ * @author Paul Sandoz
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("/helloworld")
+@RolesAllowed("myRole")
+public class HelloWorldResource {
+
+ /**
+ * HTTP GET controller method.
+ *
+ * @param request HTTP servlet request.
+ * @return a simple text message.
+ */
+ @GET
+ @Produces("text/plain")
+ public Response getClichedMessage(@Context HttpServletRequest request) {
+ return Response
+ .status(Response.Status.OK)
+ .type("text/plain")
+ .entity("Sending \"Hello World\" to user \"" + request.getUserPrincipal().getName() + "\"")
+ .build();
+
+ }
+}
diff --git a/examples/https-server-glassfish/src/main/webapp/WEB-INF/glassfish-web.xml b/examples/https-server-glassfish/src/main/webapp/WEB-INF/glassfish-web.xml
new file mode 100644
index 0000000..4def0b8
--- /dev/null
+++ b/examples/https-server-glassfish/src/main/webapp/WEB-INF/glassfish-web.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
+<glassfish-web-app error-url="">
+ <context-root>/https-server-glassfish-webapp</context-root>
+
+ <security-role-mapping>
+ <!-- maps "Users" group to myRole -->
+ <role-name>myRole</role-name>
+ <group-name>Users</group-name>
+ </security-role-mapping>
+
+ <class-loader delegate="true" />
+ <jsp-config>
+ <property name="classdebuginfo" value="true">
+ <description>Enable debug info compilation in the generated servlet class</description>
+ </property>
+ <property name="keepgenerated" value="true">
+ <description>Keep a copy of the generated servlet class' java code.</description>
+ </property>
+ <property name="mappedfile" value="true">
+ <description>Maintain a one-to-one correspondence between static content and the generated servlet class' java code</description>
+ </property>
+ </jsp-config>
+</glassfish-web-app>
diff --git a/examples/https-server-glassfish/src/main/webapp/WEB-INF/web.xml b/examples/https-server-glassfish/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..9833d0f
--- /dev/null
+++ b/examples/https-server-glassfish/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+ <servlet>
+ <servlet-name>Jersey Web Application</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>jersey.config.server.provider.packages</param-name>
+ <param-value>org.glassfish.jersey.examples.https.glassfish.resources</param-value>
+ </init-param>
+ <init-param>
+ <param-name>jersey.config.server.provider.classnames</param-name>
+ <param-value>org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>Jersey Web Application</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Protected resource</web-resource-name>
+ <url-pattern>/*</url-pattern>
+ <http-method>GET</http-method>
+ </web-resource-collection>
+
+ <!-- added -->
+ <auth-constraint>
+ <!-- role name used in HelloWorldResource.java-->
+ <role-name>myRole</role-name>
+ </auth-constraint>
+ <!-- /added -->
+
+ <!-- https -->
+ <user-data-constraint>
+ <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+ </user-data-constraint>
+ </security-constraint>
+
+ <!-- added -->
+ <login-config>
+ <auth-method>BASIC</auth-method>
+ <!-- realm name used in GlassFish -->
+ <realm-name>myRealm</realm-name>
+ </login-config>
+ <security-role>
+ <role-name>myRole</role-name>
+ </security-role>
+ <!-- /added -->
+</web-app>
diff --git a/examples/java8-webapp/README.MD b/examples/java8-webapp/README.MD
new file mode 100644
index 0000000..23d2565
--- /dev/null
+++ b/examples/java8-webapp/README.MD
@@ -0,0 +1,45 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Jersey and Java 8 Examples Features and Types
+=============================================
+
+This example demonstrates the usage of Java 8's new constructs and types.
+
+Currently demonstrated are:
+
+- Default methods in interfaces - acting as JAX-RS Resource Methods
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP method | description
+-------------------------------- | ------------------------ | ------------- | ------------------------------------------
+**_/j8/default-method_** | DefaultMethodInterface | GET | returns `interface-root` in `text/plain`
+**_/j8/default-method/path_** | DefaultMethodInterface | GET | returns `interface-path` in `text/plain`
+**_/j8/default-method/class_** | DefaultMethodResource | GET | returns `class` in `text/plain`
+**_/j8/lambdas/{p}_** | LambdaResource | GET | returns `{p}-lambdaized` in `text/plain`
+
+Application is Servlet 3 based, web.xml-less. Everything needed
+(resources/providers) is registered in the `Java8Application` class.
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean package jetty:run
+
+This deploys current example using Jetty. You can access the application at:
+
+- <http://localhost:8080/j8/default-method>
+- <http://localhost:8080/j8/default-method/path>
+- <http://localhost:8080/j8/default-method/class>
+- <http://localhost:8080/j8/lambdas/test>
diff --git a/examples/java8-webapp/pom.xml b/examples/java8-webapp/pom.xml
new file mode 100644
index 0000000..0b4ac28
--- /dev/null
+++ b/examples/java8-webapp/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>java8-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-java8-webapp</name>
+
+ <description>Java 8 Types WebApp Example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <inherited>true</inherited>
+ <configuration>
+ <source>${java.version}</source>
+ <target>${java.version}</target>
+ <showWarnings>false</showWarnings>
+ <fork>false</fork>
+ </configuration>
+ </plugin>
+ <!-- Run the application using "mvn jetty:run" -->
+ <plugin>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-maven-plugin</artifactId>
+ <!-- TODO: Remove the version when JERSEY-2743 is resolved. -->
+ <version>9.2.6.v20141205</version>
+ <configuration>
+ <scanIntervalSeconds>5</scanIntervalSeconds>
+ <stopPort>9999</stopPort>
+ <stopKey>STOP</stopKey>
+ <webApp>
+ <contextPath>/</contextPath>
+ <webInfIncludeJarPattern>.*/.*jersey-[^/]\.jar$</webInfIncludeJarPattern>
+ </webApp>
+ <systemProperties>
+ <systemProperty>
+ <name>jetty.port</name>
+ <value>${jersey.config.test.container.port}</value>
+ </systemProperty>
+ </systemProperties>
+ <war>${project.build.directory}/${project.build.finalName}.war</war>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <properties>
+ <jersey.config.test.container.port>8080</jersey.config.test.container.port>
+ </properties>
+</project>
diff --git a/examples/java8-webapp/src/main/java/org/glassfish/jersey/examples/java8/Java8Application.java b/examples/java8-webapp/src/main/java/org/glassfish/jersey/examples/java8/Java8Application.java
new file mode 100644
index 0000000..e61c5ad
--- /dev/null
+++ b/examples/java8-webapp/src/main/java/org/glassfish/jersey/examples/java8/Java8Application.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.java8;
+
+import javax.ws.rs.ApplicationPath;
+
+import org.glassfish.jersey.examples.java8.resources.DefaultMethodResource;
+import org.glassfish.jersey.examples.java8.resources.LambdaResource;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * Application for illustrating some of the features of Java 8 in JAX-RS.
+ *
+ * @author Michal Gajdos
+ */
+@ApplicationPath("j8")
+public class Java8Application extends ResourceConfig {
+
+ public Java8Application() {
+ // Resources.
+ register(DefaultMethodResource.class);
+ register(LambdaResource.class);
+ }
+}
diff --git a/examples/java8-webapp/src/main/java/org/glassfish/jersey/examples/java8/resources/DefaultMethodInterface.java b/examples/java8-webapp/src/main/java/org/glassfish/jersey/examples/java8/resources/DefaultMethodInterface.java
new file mode 100644
index 0000000..4a67e9f
--- /dev/null
+++ b/examples/java8-webapp/src/main/java/org/glassfish/jersey/examples/java8/resources/DefaultMethodInterface.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.java8.resources;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+/**
+ * Example interface containing resource methods in form of Java8's default methods.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public interface DefaultMethodInterface {
+
+ @GET
+ default String root() {
+ return "interface-root";
+ }
+
+ @GET
+ @Path("path")
+ default String path() {
+ return "interface-path";
+ }
+}
+
diff --git a/examples/java8-webapp/src/main/java/org/glassfish/jersey/examples/java8/resources/DefaultMethodResource.java b/examples/java8-webapp/src/main/java/org/glassfish/jersey/examples/java8/resources/DefaultMethodResource.java
new file mode 100644
index 0000000..79e347d
--- /dev/null
+++ b/examples/java8-webapp/src/main/java/org/glassfish/jersey/examples/java8/resources/DefaultMethodResource.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.java8.resources;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * JAX-RS resource inheriting some resource method implementations from the implemented interface.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@Path("default-method")
+@Produces("text/plain")
+public class DefaultMethodResource implements DefaultMethodInterface {
+
+ @GET
+ @Path("class")
+ public String fromClass() {
+ return "class";
+ }
+}
diff --git a/examples/java8-webapp/src/main/java/org/glassfish/jersey/examples/java8/resources/LambdaResource.java b/examples/java8-webapp/src/main/java/org/glassfish/jersey/examples/java8/resources/LambdaResource.java
new file mode 100644
index 0000000..2448ea8
--- /dev/null
+++ b/examples/java8-webapp/src/main/java/org/glassfish/jersey/examples/java8/resources/LambdaResource.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.java8.resources;
+
+import java.util.Collections;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+
+/**
+ * JAX-RS resource using Java SE 8 lambdas.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("lambdas/{p}")
+public class LambdaResource {
+ @GET
+ public String getLambdaResult(@PathParam("p") String p) {
+ return Collections.singleton(p).stream().map(v -> v + "-lambdaized").collect(Collectors.joining());
+ }
+}
diff --git a/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/DefaultMethodResourceTest.java b/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/DefaultMethodResourceTest.java
new file mode 100644
index 0000000..bd187ef
--- /dev/null
+++ b/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/DefaultMethodResourceTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.java8;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test usage of Java8's interface default methods as resource methods.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public class DefaultMethodResourceTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ return new Java8Application();
+ }
+
+ /**
+ * Test that JDK8 default methods do work as common JAX-RS resource methods.
+ */
+ @Test
+ public void testDefaultMethods() {
+ final WebTarget defaultMethodTarget = target("default-method");
+
+ // test default method with no @Path annotation
+ String response = defaultMethodTarget.request().get(String.class);
+ assertEquals("interface-root", response);
+
+ // test default method with with @Path annotation
+ response = defaultMethodTarget.path("path").request().get(String.class);
+ assertEquals("interface-path", response);
+ }
+
+ /**
+ * Test, that resource methods defined in the class implementing the interface with default method do work normally.
+ */
+ @Test
+ public void testImplementingClass() throws Exception {
+ final String response = target("default-method").path("class").request().get(String.class);
+ assertEquals("class", response);
+ }
+}
diff --git a/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/LambdaResourceTest.java b/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/LambdaResourceTest.java
new file mode 100644
index 0000000..937b1c7
--- /dev/null
+++ b/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/LambdaResourceTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.java8;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Test usage of Java SE 8 lambdas in JAX-RS resource methods.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class LambdaResourceTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ return new Java8Application();
+ }
+
+ /**
+ * Test that JDK8 lambdas do work in common JAX-RS resource methods.
+ */
+ @Test
+ public void testLambdas() {
+ final WebTarget target = target("lambdas/{p}");
+
+ // test default method with no @Path annotation
+ String response = target.resolveTemplate("p", "test").request().get(String.class);
+ assertThat(response, equalTo("test-lambdaized"));
+ }
+
+}
diff --git a/examples/jaxb/README.MD b/examples/jaxb/README.MD
new file mode 100644
index 0000000..fcd216e
--- /dev/null
+++ b/examples/jaxb/README.MD
@@ -0,0 +1,73 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+JAXB Example
+============
+
+This example demonstrates how to use JAXB types with resource classes and the JAX-RS Client API.
+
+The following JAXB types are supported and demonstrated:
+
+- JAXB classes annotated with @XmlRootElement
+- JAXB classes annotated with @XmlType
+- JAXBElement<T> where T is a JAXB xml type that may or may not be annotated with @XmlType
+- List<T>, Collection<T>, and T\[\] where T is a JAXB class annotated with @XmlRootElement or @XmlType
+
+It also shows how to use @XmlHeader annotation to specify the header of the XML responses.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+--------------------------------------- | ------------------------ | --------------
+**_/jaxb/XmlRootElement_** | JaxbResource | GET, POST
+**_/jaxb/XmlRootElementWithHeader_** | JaxbResource | GET
+**_/jaxb/JAXBElement_** | JaxbResource | GET, POST
+**_/jaxb/XmlType_** | JaxbResource | POST
+**_/jaxb/collection/XmlRootElement_** | JaxbCollectionResource | GET, POST
+**_/jaxb/collection/XmlType_** | JaxbCollectionResource | POST
+**_/jaxb/array/XmlRootElement_** | JaxbArrayResource | GET, POST
+**_/jaxb/array/XmlType_** | JaxbArrayResource | POST
+
+JAXB classes annotated with @XmlRootElement may be produced and
+consumed. JAXB classses annotated with @XmlType can only be consumed
+hence why only the POST method is supported for the resources associated
+with XmlType. This is because the JAXB requires enough information to
+serialize the JAXB instance to a well-formed XML document. The same
+rules apply to collections of such classes.
+
+For the serialization of a Collection of JAXB type, or an array, Jersey
+will automatically derive the XML root element of the XML document to
+serialize from the XML name of the JAXB class annoated with
+@XmlRootElement.
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container. You can access the application at:
+
+- <http://localhost:8080/jaxb/jaxb/XmlRootElement>
+- <http://localhost:8080/jaxb/jaxb/XmlRootElementWithHeader>
+- <http://localhost:8080/jaxb/jaxb/JAXBElement>
+- <http://localhost:8080/jaxb/jaxb/collection/XmlRootElement>
+- <http://localhost:8080/jaxb/jaxb/array/XmlRootElement>
+
+or you can post an xml entity:
+
+> curl -v -X POST http://localhost:8080/jaxb/jaxb/XmlRootElement -H "Content-Type:application/xml" -d '
+> <jaxbXmlRootElement>
+> <value>xml root element</value>
+> </jaxbXmlRootElement>'
+
+You can see test classes at `src/main/test` subdirectory for detailed information how to consume the service using JAX-RS Client API
\ No newline at end of file
diff --git a/examples/jaxb/pom.xml b/examples/jaxb/pom.xml
new file mode 100644
index 0000000..47f856d
--- /dev/null
+++ b/examples/jaxb/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jaxb</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-jaxb</name>
+
+ <description>Jersey JAXB example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.woodstox</groupId>
+ <artifactId>woodstox-core-asl</artifactId>
+ <version>4.1.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.jaxb.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/App.java b/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/App.java
new file mode 100644
index 0000000..c07a92b
--- /dev/null
+++ b/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/App.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jaxb;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Jersey JAXB example application.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/jaxb/");
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("JAXB Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, createApp(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(
+ String.format("Application started.%nTry out %s%nStop the application using CTRL+C", BASE_URI));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ public static ResourceConfig createApp() {
+ final ResourceConfig rc = new ResourceConfig()
+ .packages(JaxbResource.class.getPackage().getName());
+
+ return rc;
+ }
+}
diff --git a/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbArrayResource.java b/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbArrayResource.java
new file mode 100644
index 0000000..9242c53
--- /dev/null
+++ b/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbArrayResource.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jaxb;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * An example resource utilizing array of JAXB beans.
+ *
+ * @author Paul Sandoz
+ */
+@Path("jaxb/array")
+@Produces("application/xml")
+@Consumes("application/xml")
+public class JaxbArrayResource {
+
+ @Path("XmlRootElement")
+ @GET
+ public JaxbXmlRootElement[] getRootElement() {
+ List<JaxbXmlRootElement> el = new ArrayList<JaxbXmlRootElement>();
+ el.add(new JaxbXmlRootElement("one root element"));
+ el.add(new JaxbXmlRootElement("two root element"));
+ el.add(new JaxbXmlRootElement("three root element"));
+ return el.toArray(new JaxbXmlRootElement[el.size()]);
+ }
+
+ @Path("XmlRootElement")
+ @POST
+ public JaxbXmlRootElement[] postRootElement(JaxbXmlRootElement[] el) {
+ return el;
+ }
+
+ @Path("XmlType")
+ @POST
+ public JaxbXmlRootElement[] postXmlType(JaxbXmlType[] tl) {
+ List<JaxbXmlRootElement> el = new ArrayList<JaxbXmlRootElement>();
+
+ for (JaxbXmlType t : tl) {
+ el.add(new JaxbXmlRootElement(t.value));
+ }
+
+ return el.toArray(new JaxbXmlRootElement[el.size()]);
+ }
+}
diff --git a/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbCollectionResource.java b/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbCollectionResource.java
new file mode 100644
index 0000000..eb0e64e
--- /dev/null
+++ b/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbCollectionResource.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jaxb;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * An example resource utilizing collections of JAXB beans.
+ *
+ * @author Paul Sandoz
+ */
+@Path("jaxb/collection")
+@Produces("application/xml")
+@Consumes("application/xml")
+public class JaxbCollectionResource {
+
+ @Path("XmlRootElement")
+ @GET
+ public List<JaxbXmlRootElement> getRootElement() {
+ List<JaxbXmlRootElement> el = new ArrayList<JaxbXmlRootElement>();
+ el.add(new JaxbXmlRootElement("one root element"));
+ el.add(new JaxbXmlRootElement("two root element"));
+ el.add(new JaxbXmlRootElement("three root element"));
+ return el;
+ }
+
+ @Path("XmlRootElement")
+ @POST
+ public Collection<JaxbXmlRootElement> postRootElement(List<JaxbXmlRootElement> el) {
+ return el;
+ }
+
+ @Path("XmlType")
+ @POST
+ public List<JaxbXmlRootElement> postXmlType(List<JaxbXmlType> tl) {
+ List<JaxbXmlRootElement> el = new ArrayList<JaxbXmlRootElement>();
+
+ for (JaxbXmlType t : tl) {
+ el.add(new JaxbXmlRootElement(t.value));
+ }
+
+ return el;
+ }
+}
diff --git a/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbResource.java b/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbResource.java
new file mode 100644
index 0000000..b4cd435
--- /dev/null
+++ b/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbResource.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jaxb;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.namespace.QName;
+
+import org.glassfish.jersey.message.XmlHeader;
+
+/**
+ * An example resource utilizing JAXB beans.
+ *
+ * @author Paul Sandoz
+ */
+@Path("jaxb")
+@Produces("application/xml")
+@Consumes("application/xml")
+public class JaxbResource {
+
+ @Path("XmlRootElement")
+ @GET
+ public JaxbXmlRootElement getRootElement() {
+ return new JaxbXmlRootElement("xml root element");
+ }
+
+ @Path("XmlRootElementWithHeader")
+ @GET
+ @XmlHeader("<?xml-stylesheet type='text/xsl' href='foobar.xsl' ?>")
+ public JaxbXmlRootElement getRootElementWithHeader() {
+ return new JaxbXmlRootElement("xml root element");
+ }
+
+ @Path("XmlRootElement")
+ @POST
+ public JaxbXmlRootElement postRootElement(JaxbXmlRootElement r) {
+ return r;
+ }
+
+ @Path("JAXBElement")
+ @GET
+ public JAXBElement<JaxbXmlType> getJAXBElement() {
+ return new JAXBElement<JaxbXmlType>(
+ new QName("jaxbXmlRootElement"),
+ JaxbXmlType.class,
+ new JaxbXmlType("xml type"));
+ }
+
+ @Path("JAXBElement")
+ @POST
+ public JAXBElement<JaxbXmlType> postJAXBElement(JAXBElement<JaxbXmlType> e) {
+ return e;
+ }
+
+ @Path("XmlType")
+ @POST
+ public JAXBElement<JaxbXmlType> postXmlType(JaxbXmlType r) {
+ return new JAXBElement<JaxbXmlType>(
+ new QName("jaxbXmlRootElement"), JaxbXmlType.class, r);
+ }
+}
diff --git a/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbXmlRootElement.java b/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbXmlRootElement.java
new file mode 100644
index 0000000..ceb6294
--- /dev/null
+++ b/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbXmlRootElement.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jaxb;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class JaxbXmlRootElement {
+
+ public String value;
+
+ public JaxbXmlRootElement() {
+ }
+
+ public JaxbXmlRootElement(String str) {
+ value = str;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof JaxbXmlRootElement)) {
+ return false;
+ }
+ return ((JaxbXmlRootElement) o).value.equals(value);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 89 * hash + (this.value != null ? this.value.hashCode() : 0);
+ return hash;
+ }
+}
diff --git a/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbXmlType.java b/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbXmlType.java
new file mode 100644
index 0000000..d09bacc
--- /dev/null
+++ b/examples/jaxb/src/main/java/org/glassfish/jersey/examples/jaxb/JaxbXmlType.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jaxb;
+
+import javax.xml.bind.annotation.XmlType;
+
+@XmlType
+public class JaxbXmlType {
+
+ public String value;
+
+ public JaxbXmlType() {
+ }
+
+ public JaxbXmlType(String str) {
+ value = str;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof JaxbXmlType)) {
+ return false;
+ }
+ return ((JaxbXmlType) o).value.equals(value);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 79 * hash + (this.value != null ? this.value.hashCode() : 0);
+ return hash;
+ }
+}
diff --git a/examples/jaxb/src/test/java/org/glassfish/jersey/examples/jaxb/JaxbTest.java b/examples/jaxb/src/test/java/org/glassfish/jersey/examples/jaxb/JaxbTest.java
new file mode 100644
index 0000000..e6f3a8c
--- /dev/null
+++ b/examples/jaxb/src/test/java/org/glassfish/jersey/examples/jaxb/JaxbTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jaxb;
+
+import java.util.Collection;
+
+import javax.ws.rs.core.GenericEntity;
+import javax.ws.rs.core.GenericType;
+import static javax.ws.rs.client.Entity.xml;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.namespace.QName;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Jersey JAXB example test.
+ *
+ * @author Paul Sandoz
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class JaxbTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ enable(TestProperties.DUMP_ENTITY);
+
+ return App.createApp();
+ }
+
+ /**
+ * Test checks that the application.wadl is reachable.
+ */
+ @Test
+ public void testApplicationWadl() {
+ String applicationWadl = target().path("application.wadl").request().get(String.class);
+ assertTrue("Something wrong. Returned wadl length is not > 0",
+ applicationWadl.length() > 0);
+ }
+
+ @Test
+ public void testRootElement() {
+ JaxbXmlRootElement e1 = target().path("jaxb/XmlRootElement").request().get(JaxbXmlRootElement.class);
+
+ JaxbXmlRootElement e2 = target().path("jaxb/XmlRootElement").request("application/xml")
+ .post(xml(e1), JaxbXmlRootElement.class);
+
+ assertEquals(e1, e2);
+ }
+
+ @Test
+ public void testRootElementWithHeader() {
+ String e1 = target().path("jaxb/XmlRootElement").request().get(String.class);
+
+ String e2 = target().path("jaxb/XmlRootElementWithHeader").request().get(String.class);
+ assertTrue(e2.contains("<?xml-stylesheet type='text/xsl' href='foobar.xsl' ?>") && e2.contains(e1.substring(e1.indexOf("?>") + 2).trim()));
+ }
+
+ @Test
+ public void testJAXBElement() {
+ GenericType<JAXBElement<JaxbXmlType>> genericType = new GenericType<JAXBElement<JaxbXmlType>>() {};
+
+ JAXBElement<JaxbXmlType> e1 = target().path("jaxb/JAXBElement").request().get(genericType);
+
+ JAXBElement<JaxbXmlType> e2 = target().path("jaxb/JAXBElement").request("application/xml")
+ .post(xml(e1), genericType);
+
+ assertEquals(e1.getValue(), e2.getValue());
+ }
+
+ @Test
+ public void testXmlType() {
+ JaxbXmlType t1 = target().path("jaxb/JAXBElement").request().get(JaxbXmlType.class);
+
+ JAXBElement<JaxbXmlType> e = new JAXBElement<JaxbXmlType>(
+ new QName("jaxbXmlRootElement"),
+ JaxbXmlType.class,
+ t1);
+ JaxbXmlType t2 = target().path("jaxb/XmlType").request("application/xml")
+ .post(xml(e), JaxbXmlType.class);
+
+ assertEquals(t1, t2);
+ }
+
+
+ @Test
+ public void testRootElementCollection() {
+ GenericType<Collection<JaxbXmlRootElement>> genericType =
+ new GenericType<Collection<JaxbXmlRootElement>>() {};
+
+ Collection<JaxbXmlRootElement> ce1 = target().path("jaxb/collection/XmlRootElement").request().get(genericType);
+ Collection<JaxbXmlRootElement> ce2 = target().path("jaxb/collection/XmlRootElement").request("application/xml")
+ .post(xml(new GenericEntity<Collection<JaxbXmlRootElement>>(ce1) {}), genericType);
+
+ assertEquals(ce1, ce2);
+ }
+
+ @Test
+ public void testXmlTypeCollection() {
+ GenericType<Collection<JaxbXmlRootElement>> genericRootElement =
+ new GenericType<Collection<JaxbXmlRootElement>>() {};
+ GenericType<Collection<JaxbXmlType>> genericXmlType =
+ new GenericType<Collection<JaxbXmlType>>() {
+ };
+
+ Collection<JaxbXmlRootElement> ce1 = target().path("jaxb/collection/XmlRootElement").request()
+ .get(genericRootElement);
+
+ Collection<JaxbXmlType> ct1 = target().path("jaxb/collection/XmlType").request("application/xml")
+ .post(xml(new GenericEntity<Collection<JaxbXmlRootElement>>(ce1) {}), genericXmlType);
+
+ Collection<JaxbXmlType> ct2 = target().path("jaxb/collection/XmlRootElement").request()
+ .get(genericXmlType);
+
+ assertEquals(ct1, ct2);
+ }
+
+ @Test
+ public void testRootElementArray() {
+ JaxbXmlRootElement[] ae1 = target().path("jaxb/array/XmlRootElement").request()
+ .get(JaxbXmlRootElement[].class);
+ JaxbXmlRootElement[] ae2 = target().path("jaxb/array/XmlRootElement").request("application/xml")
+ .post(xml(ae1), JaxbXmlRootElement[].class);
+
+ assertEquals(ae1.length, ae2.length);
+ for (int i = 0; i < ae1.length; i++) {
+ assertEquals(ae1[i], ae2[i]);
+ }
+ }
+
+ @Test
+ public void testXmlTypeArray() {
+ JaxbXmlRootElement[] ae1 = target().path("jaxb/array/XmlRootElement").request()
+ .get(JaxbXmlRootElement[].class);
+
+ JaxbXmlType[] at1 = target().path("jaxb/array/XmlType").request("application/xml")
+ .post(xml(ae1), JaxbXmlType[].class);
+
+ JaxbXmlType[] at2 = target().path("jaxb/array/XmlRootElement").request()
+ .get(JaxbXmlType[].class);
+
+ assertEquals(at1.length, at2.length);
+ for (int i = 0; i < at1.length; i++) {
+ assertEquals(at1[i], at2[i]);
+ }
+ }
+}
diff --git a/examples/jaxrs-types-injection/README.MD b/examples/jaxrs-types-injection/README.MD
new file mode 100644
index 0000000..a07f51b
--- /dev/null
+++ b/examples/jaxrs-types-injection/README.MD
@@ -0,0 +1,58 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+JAX-RS Types Injection Example
+==============================
+
+This example demonstrates injection of various runtime information into Jersey/JAX-RS resources.
+
+A simple application consists of two simple resources: One is a typical
+JAX-RS `@Path` annotated resource. The second resource is created
+programmatically using the new Jeresy 2 programmatic resource binding
+API. Both resources inject the same information and produce the report
+that is returned in the response back to the client.
+
+Contents
+--------
+
+The JAX-RS `@Path` annotated web resource is implemented by the
+
+`org.glassfish.jersey.examples.jaxrstypeinjection.JaxrsInjectionReportingResource`
+
+class. The programmatic resource is implemented by the
+
+`org.glassfish.jersey.examples.jaxrstypeinjection.JaxrsInjectionReportingInflector` class.
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+---------------------------------------------------------------------------------------- | ---------------------------------- | --------------
+**_/annotated/instance/{p1}/{p2}?q1=<int_value>&q2=<string_value>&q2=<string_value>_** | JaxrsInjectionReportingResource | GET
+**_/annotated/method/{p1}/{p2}?q1=<int_value>&q2=<string_value>&q2=<string_value>_** | JaxrsInjectionReportingResource | GET
+**_/programmatic/{p1}/{p2}?q1=<int_value>&q2=<string_value>&q2=<string_value>_** | JaxrsInjectionReportingInflector | GET
+
+Please note that the value of the `q1` URI query parameter is expected
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the JAX-RS Types Injection Example using [Grizzly](http://grizzly.java.net/)
+
+A [WADL description](http://wadl.java.net/#spec) may be accessed at the URL:
+
+- <http://localhost:8080/jaxrs-type-injection/application.wadl>
+
+The resources could be accessed via
+
+- <http://localhost:8080/jaxrs-type-injection/annotated/instance/p1/p2?q1=123&q2=abc&q2=def>
+- <http://localhost:8080/jaxrs-type-injection/annotated/method/p1/p2?q1=123&q2=abc&q2=def>
+- <http://localhost:8080/jaxrs-type-injection/programmatic/p1/p2?q1=123&q2=abc&q2=def>
diff --git a/examples/jaxrs-types-injection/pom.xml b/examples/jaxrs-types-injection/pom.xml
new file mode 100644
index 0000000..e026912
--- /dev/null
+++ b/examples/jaxrs-types-injection/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jaxrs-types-injection</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-jaxrs-types-injection</name>
+
+ <description>Jersey JAX-RS types injection example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.jaxrstypeinjection.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/jaxrs-types-injection/src/main/java/org/glassfish/jersey/examples/jaxrstypeinjection/App.java b/examples/jaxrs-types-injection/src/main/java/org/glassfish/jersey/examples/jaxrstypeinjection/App.java
new file mode 100644
index 0000000..d2b2ab6
--- /dev/null
+++ b/examples/jaxrs-types-injection/src/main/java/org/glassfish/jersey/examples/jaxrstypeinjection/App.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jaxrstypeinjection;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.model.Resource;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Jersey application that demonstrates injection of JAX-RS components into resources.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/jaxrs-type-injection/");
+ /**
+ * Programmatic root resource path template.
+ */
+ public static final String ROOT_PATH_PROGRAMMATIC = "programmatic/{p1}/{p2}";
+ /**
+ * Annotated class-based root resource path template demonstrating instance field injection.
+ */
+ public static final String ROOT_PATH_ANNOTATED_INSTANCE = "annotated/instance/{p1}/{p2}";
+ /**
+ * Annotated class-based root resource path template demonstrating method injection.
+ */
+ public static final String ROOT_PATH_ANNOTATED_METHOD = "annotated/method/{p1}/{p2}";
+
+ /**
+ * Main application entry point.
+ *
+ * @param args application arguments.
+ */
+ public static void main(String[] args) {
+ try {
+ System.out.println("JAX-RS Type Injection Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, create(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format(
+ "Application started.%n"
+ + "To test injection into a programmatic resource, try out:%n %s%s%s%n"
+ + "To test instance injection into an annotated resource, try out:%n %s%s%s%n"
+ + "To test method injection into an annotated resource, try out:%n %s%s%s%n"
+ + "Stop the application using CTRL+C",
+ BASE_URI, ROOT_PATH_PROGRAMMATIC, "?q1=<value_1>&q2=<value_2>&q2=<value_3>",
+ BASE_URI, ROOT_PATH_ANNOTATED_INSTANCE, "?q1=<value_1>&q2=<value_2>&q2=<value_3>",
+ BASE_URI, ROOT_PATH_ANNOTATED_METHOD, "?q1=<value_1>&q2=<value_2>&q2=<value_3>"));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Create example application resource configuration.
+ *
+ * @return initialized resource configuration of the example application.
+ */
+ public static ResourceConfig create() {
+ final ResourceConfig resourceConfig = new ResourceConfig(JaxrsInjectionReportingResource.class);
+ final Resource.Builder resourceBuilder = Resource.builder(ROOT_PATH_PROGRAMMATIC);
+ resourceBuilder.addMethod("GET").handledBy(JaxrsInjectionReportingInflector.class);
+
+ return resourceConfig.registerResources(resourceBuilder.build());
+ }
+}
diff --git a/examples/jaxrs-types-injection/src/main/java/org/glassfish/jersey/examples/jaxrstypeinjection/JaxrsInjectionReportingInflector.java b/examples/jaxrs-types-injection/src/main/java/org/glassfish/jersey/examples/jaxrstypeinjection/JaxrsInjectionReportingInflector.java
new file mode 100644
index 0000000..a77ab8f
--- /dev/null
+++ b/examples/jaxrs-types-injection/src/main/java/org/glassfish/jersey/examples/jaxrstypeinjection/JaxrsInjectionReportingInflector.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jaxrstypeinjection;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.glassfish.jersey.process.Inflector;
+
+/**
+ * Programmatic resource.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+class JaxrsInjectionReportingInflector implements Inflector<Request, Response> {
+
+ @Inject
+ HttpHeaders httpHeaders;
+ @Inject
+ UriInfo uriInfo;
+ @PathParam(value = "p1")
+ String p1;
+ private PathSegment p2;
+
+ @PathParam(value = "p2")
+ public void setP2(PathSegment p2) {
+ this.p2 = p2;
+ }
+ @QueryParam(value = "q1")
+ private int q1;
+ @QueryParam(value = "q2")
+ private List<String> q2;
+
+ @Override
+ public Response apply(Request data) {
+ StringBuilder sb = ReportBuilder.append(
+ new StringBuilder("Injected information:\n"), uriInfo, httpHeaders);
+ sb.append("\n URI component injection:");
+ sb.append("\n String path param p1=").append(p1);
+ sb.append("\n PathSegment path param p2=").append(p2);
+ sb.append("\n int query param q1=").append(q1);
+ sb.append("\n List<String> query param q2=").append(q2);
+ return Response.ok(sb.toString(), MediaType.TEXT_PLAIN).build();
+ }
+}
diff --git a/examples/jaxrs-types-injection/src/main/java/org/glassfish/jersey/examples/jaxrstypeinjection/JaxrsInjectionReportingResource.java b/examples/jaxrs-types-injection/src/main/java/org/glassfish/jersey/examples/jaxrstypeinjection/JaxrsInjectionReportingResource.java
new file mode 100644
index 0000000..87f03d9
--- /dev/null
+++ b/examples/jaxrs-types-injection/src/main/java/org/glassfish/jersey/examples/jaxrstypeinjection/JaxrsInjectionReportingResource.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jaxrstypeinjection;
+
+import java.util.List;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * Annotated resource.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("annotated")
+public class JaxrsInjectionReportingResource {
+
+ @Context
+ HttpHeaders httpHeaders;
+ @Context
+ UriInfo uriInfo;
+ @PathParam(value = "p1")
+ String p1;
+ private PathSegment p2;
+
+ @PathParam(value = "p2")
+ public void setP2(PathSegment p2) {
+ this.p2 = p2;
+ }
+
+ @QueryParam(value = "q1")
+ private int q1;
+ @QueryParam(value = "q2")
+ private List<String> q2;
+
+ @GET
+ @Path("method/{p1}/{p2}")
+ public String doGet(
+ @Context HttpHeaders httpHeaders,
+ @Context UriInfo uriInfo,
+ @PathParam(value = "p1") String p1,
+ @PathParam(value = "p2") PathSegment p2,
+ @QueryParam(value = "q1") int q1,
+ @QueryParam(value = "q2") List<String> q2) {
+ StringBuilder sb = ReportBuilder.append(
+ new StringBuilder("Injected information:\n"), uriInfo, httpHeaders);
+ sb.append("\n URI component injection:");
+ sb.append("\n String path param p1=").append(p1);
+ sb.append("\n PathSegment path param p2=").append(p2);
+ sb.append("\n int query param q1=").append(q1);
+ sb.append("\n List<String> query param q2=").append(q2);
+ return sb.toString();
+ }
+
+ @GET
+ @Path("instance/{p1}/{p2}")
+ public String doGet() {
+ return doGet(httpHeaders, uriInfo, p1, p2, q1, q2);
+ }
+}
diff --git a/examples/jaxrs-types-injection/src/main/java/org/glassfish/jersey/examples/jaxrstypeinjection/ReportBuilder.java b/examples/jaxrs-types-injection/src/main/java/org/glassfish/jersey/examples/jaxrstypeinjection/ReportBuilder.java
new file mode 100644
index 0000000..fcafa26
--- /dev/null
+++ b/examples/jaxrs-types-injection/src/main/java/org/glassfish/jersey/examples/jaxrstypeinjection/ReportBuilder.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jaxrstypeinjection;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * Provides functionality for appending values of JAX-RS types to a string-based
+ * report.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+class ReportBuilder {
+
+ private ReportBuilder() {
+ }
+
+ public static StringBuilder append(StringBuilder sb, UriInfo uriInfo, HttpHeaders httpHeaders) {
+ sb.append("\n UriInfo:");
+ sb.append("\n Absolute path : ").append(uriInfo.getAbsolutePath());
+ sb.append("\n Base URI : ").append(uriInfo.getBaseUri());
+ sb.append("\n Mathced resources : ").append(uriInfo.getMatchedResources().toString());
+ sb.append("\n Matched URIs : ").append(uriInfo.getMatchedURIs().toString());
+ sb.append("\n Path : ").append(uriInfo.getPath());
+ sb.append("\n Path parameters:\n");
+ dumpMultivaluedMap(sb, uriInfo.getPathParameters());
+ sb.append(" Path segments : ").append(uriInfo.getPathSegments().toString());
+ sb.append("\n Query parameters:\n");
+ dumpMultivaluedMap(sb, uriInfo.getQueryParameters());
+ sb.append(" Request URI : ").append(uriInfo.getRequestUri());
+ sb.append("\n\n HttpHeaders:\n");
+ dumpMultivaluedMap(sb, httpHeaders.getRequestHeaders());
+ return sb;
+ }
+
+ public static void dumpMultivaluedMap(StringBuilder sb, MultivaluedMap<String, String> map) {
+ if (map == null) {
+ sb.append(" [ null ]\n");
+ return;
+ }
+ for (Map.Entry<String, List<String>> headerEntry : map.entrySet()) {
+ sb.append(" ").append(headerEntry.getKey()).append(" : ");
+ final Iterator<String> valueIterator = headerEntry.getValue().iterator();
+ sb.append(valueIterator.next());
+ while (valueIterator.hasNext()) {
+ sb.append(", ").append(valueIterator.next());
+ }
+ sb.append('\n');
+ }
+ }
+}
diff --git a/examples/jaxrs-types-injection/src/test/java/org/glassfish/jersey/examples/jaxrstypeinjection/JaxrsTypeInjectionTest.java b/examples/jaxrs-types-injection/src/test/java/org/glassfish/jersey/examples/jaxrstypeinjection/JaxrsTypeInjectionTest.java
new file mode 100644
index 0000000..d9e87e1
--- /dev/null
+++ b/examples/jaxrs-types-injection/src/test/java/org/glassfish/jersey/examples/jaxrstypeinjection/JaxrsTypeInjectionTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jaxrstypeinjection;
+
+import javax.ws.rs.client.WebTarget;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+
+public class JaxrsTypeInjectionTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ return App.create();
+ }
+
+ private String[] expectedFragmentsProgrammatic = new String[]{
+ // UriInfo
+ "Absolute path : " + this.getBaseUri() + "programmatic/v1/v2",
+ "Base URI : " + this.getBaseUri(),
+ "Path : programmatic/v1/v2",
+ "Path segments : [programmatic, v1, v2]",
+ "p1 : v1", "p2 : v2", // path params
+ "q1 : 1", "q2 : v2, v3", // query params
+ "Request URI : " + this.getBaseUri() + "programmatic/v1/v2?q1=1&q2=v2&q2=v3",
+ // RequestHeaders/HttpHeaders
+ "Accept : text/plain",
+ // Injected Parameters
+ "String path param p1=v1",
+ "PathSegment path param p2=v2",
+ "int query param q1=1",
+ "List<String> query param q2=[v2, v3]"
+ };
+ private String[] expectedFragmentsAnnotatedInstance = new String[]{
+ // UriInfo
+ "Absolute path : " + this.getBaseUri() + "annotated/instance/v1/v2",
+ "Base URI : " + this.getBaseUri(),
+ "Path : annotated/instance/v1/v2",
+ "Path segments : [annotated, instance, v1, v2]",
+ "p1 : v1", "p2 : v2", // path params
+ "q1 : 1", "q2 : v2, v3", // query params
+ "Request URI : " + this.getBaseUri() + "annotated/instance/v1/v2?q1=1&q2=v2&q2=v3",
+ // RequestHeaders/HttpHeaders
+ "Accept : text/plain",
+ // Injected Parameters
+ "String path param p1=v1",
+ "PathSegment path param p2=v2",
+ "int query param q1=1",
+ "List<String> query param q2=[v2, v3]"
+ };
+ private String[] expectedFragmentsAnnotatedMethod = new String[]{
+ // UriInfo
+ "Absolute path : " + this.getBaseUri() + "annotated/method/v1/v2",
+ "Base URI : " + this.getBaseUri(),
+ "Path : annotated/method/v1/v2",
+ "Path segments : [annotated, method, v1, v2]",
+ "p1 : v1", "p2 : v2", // path params
+ "q1 : 1", "q2 : v2, v3", // query params
+ "Request URI : " + this.getBaseUri() + "annotated/method/v1/v2?q1=1&q2=v2&q2=v3",
+ // RequestHeaders/HttpHeaders
+ "Accept : text/plain",
+ // Injected Parameters
+ "String path param p1=v1",
+ "PathSegment path param p2=v2",
+ "int query param q1=1",
+ "List<String> query param q2=[v2, v3]"
+ };
+
+ private WebTarget prepareTarget(String path) {
+ final WebTarget target = target();
+ target.register(LoggingFeature.class);
+ return target.path(path).resolveTemplate("p1", "v1").resolveTemplate("p2",
+ "v2").queryParam("q1", 1).queryParam("q2", "v2").queryParam("q2", "v3");
+ }
+
+ @Test
+ public void testProgrammaticApp() throws Exception {
+ String responseEntity = prepareTarget(App.ROOT_PATH_PROGRAMMATIC).request("text/plain").get(String.class)
+ .toLowerCase();
+
+ for (String expectedFragment : expectedFragmentsProgrammatic) {
+ assertTrue("Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity,
+ // http header field names are case insensitive
+ responseEntity.contains(expectedFragment.toLowerCase()));
+ }
+ }
+
+ @Test
+ public void testAnnotatedInstanceApp() throws Exception {
+ String responseEntity = prepareTarget(App.ROOT_PATH_ANNOTATED_INSTANCE).request("text/plain").get(String.class)
+ .toLowerCase();
+
+ for (String expectedFragment : expectedFragmentsAnnotatedInstance) {
+ assertTrue("Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity,
+ // http header field names are case insensitive
+ responseEntity.contains(expectedFragment.toLowerCase()));
+ }
+ }
+
+ @Test
+ public void testAnnotatedMethodApp() throws Exception {
+ String responseEntity = prepareTarget(App.ROOT_PATH_ANNOTATED_METHOD).request("text/plain").get(String.class)
+ .toLowerCase();
+
+ for (String expectedFragment : expectedFragmentsAnnotatedMethod) {
+ assertTrue("Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity,
+ // http header field names are case insensitive
+ responseEntity.contains(expectedFragment.toLowerCase()));
+ }
+ }
+}
diff --git a/examples/jersey-ejb/README.MD b/examples/jersey-ejb/README.MD
new file mode 100644
index 0000000..259a9a6
--- /dev/null
+++ b/examples/jersey-ejb/README.MD
@@ -0,0 +1,44 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Jersey EJB Example
+==================
+
+This example demonstrates how to develop RESTful web service with a Servlet 3.0 and EJB 3.1 container.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Description
+--------------------------------------- | -------------------------- | -------------- | ------------------------------------------------------------------------------------
+**_/jersey-ejb_** | --- | GET | Simple html/ajax client
+**_/jersey-ejb/app/messages_** | MessageBoardResourceBean | GET | Returns first ten messsages
+**_/jersey-ejb/app/messages/{id}_** | MessageBoardResourceBean | GET | Returns message with uniqueId = {id} or returns HTTP 404 if message does not exist
+**_/jersey-ejb/app/messages/{id}_** | MessageBoardResourceBean | DELETE | Deletes message with uniqueId = {id} or returns HTTP 404 if message does not exist
+**_/jersey-ejb/app/messages/_** | MessageBoardResourceBean | POST | Creates new message and allocates unique id for it (with message as request entity)
+
+Running the Example
+-------------------
+
+This sample utilizes Java EE features in the GlassFish container
+
+Build the application WAR file by executing maven 2 from the project directory:
+
+> mvn clean package
+
+To deploy on a running GlassFish container, you would then typically need to launch:
+
+> $AS_HOME/bin/asadmin deploy target/jersey-ejb.war
+
+Another option is to deploy using [GlassFish administrator console GUI](http://localhost:4848/)
+
+You can access the application at:
+
+- <http://localhost:8080/jersey-ejb>
diff --git a/examples/jersey-ejb/pom.xml b/examples/jersey-ejb/pom.xml
new file mode 100644
index 0000000..2a62817
--- /dev/null
+++ b/examples/jersey-ejb/pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-ejb</artifactId>
+ <name>jersey-examples-ejb</name>
+ <packaging>war</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.ejb</groupId>
+ <artifactId>javax.ejb-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <profiles>
+ <profile>
+ <id>run-external-tests</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <jersey.config.test.container.factory>${external.container.factory}</jersey.config.test.container.factory>
+ <jersey.config.test.container.port>${external.container.port}</jersey.config.test.container.port>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <properties>
+ <!-- External test container configuration is done via properties to allow overriding via command line. -->
+ <external.container.factory>org.glassfish.jersey.test.external.ExternalTestContainerFactory</external.container.factory>
+ <external.container.port>8080</external.container.port>
+ <maven.test.skip>false</maven.test.skip>
+ </properties>
+ </profile>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <properties>
+ <maven.test.skip>true</maven.test.skip>
+ </properties>
+</project>
diff --git a/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/entities/Message.java b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/entities/Message.java
new file mode 100644
index 0000000..6bea954
--- /dev/null
+++ b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/entities/Message.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jersey_ejb.entities;
+
+import java.util.Date;
+
+/**
+ * Message bean representing a single message.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class Message {
+
+ private Date created;
+ private String message;
+ private int uniqueId;
+
+ public Message(Date created, String message, int uniqueId) {
+ this.created = created;
+ this.message = message;
+ this.uniqueId = uniqueId;
+ }
+
+ public int getUniqueId() {
+ return uniqueId;
+ }
+
+ @Override
+ public String toString() {
+ return "<span class='created'>CREATED: " + created + "</span> <span class='uniqueId'>ID: " + uniqueId
+ + "</span> <span class='message'>MESSAGE: " + message + "</span>";
+ }
+}
diff --git a/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/entities/MessageListWriter.java b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/entities/MessageListWriter.java
new file mode 100644
index 0000000..f4eef68
--- /dev/null
+++ b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/entities/MessageListWriter.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jersey_ejb.entities;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.util.List;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import javax.ejb.Stateless;
+
+import org.glassfish.jersey.message.MessageUtils;
+
+/**
+ * A simple HTML message body writer to serialize list of message beans.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Stateless
+@Provider
+public class MessageListWriter implements MessageBodyWriter<List<Message>> {
+
+ @Context
+ private javax.inject.Provider<UriInfo> ui;
+
+ @Override
+ public boolean isWriteable(final Class<?> clazz, final Type type, final Annotation[] annotation, final MediaType mediaType) {
+ return verifyGenericType(type);
+ }
+
+ private boolean verifyGenericType(final Type genericType) {
+ if (!(genericType instanceof ParameterizedType)) {
+ return false;
+ }
+
+ final ParameterizedType pt = (ParameterizedType) genericType;
+
+ if (pt.getActualTypeArguments().length > 1) {
+ return false;
+ }
+
+ if (!(pt.getActualTypeArguments()[0] instanceof Class)) {
+ return false;
+ }
+
+ final Class listClass = (Class) pt.getActualTypeArguments()[0];
+ return listClass == Message.class;
+ }
+
+ @Override
+ public long getSize(final List<Message> messages,
+ final Class<?> clazz,
+ final Type type,
+ final Annotation[] annotation,
+ final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(final List<Message> messages,
+ final Class<?> clazz,
+ final Type type,
+ final Annotation[] annotation,
+ final MediaType mediaType,
+ final MultivaluedMap<String, Object> arg5,
+ final OutputStream ostream) throws IOException, WebApplicationException {
+ for (final Message m : messages) {
+ ostream.write(m.toString().getBytes(MessageUtils.getCharset(mediaType)));
+ final URI mUri = ui.get().getAbsolutePathBuilder().path(Integer.toString(m.getUniqueId())).build();
+ ostream.write((" <a href='" + mUri.toASCIIString() + "'>link</a><br />").getBytes());
+ }
+ }
+}
diff --git a/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/entities/MessageWriter.java b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/entities/MessageWriter.java
new file mode 100644
index 0000000..5d7d2cd
--- /dev/null
+++ b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/entities/MessageWriter.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jersey_ejb.entities;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import javax.ejb.Singleton;
+import org.glassfish.jersey.message.MessageUtils;
+
+/**
+ * A simple message body writer to serialize a single message bean.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Singleton
+@Provider
+public class MessageWriter implements MessageBodyWriter<Message> {
+
+ @Override
+ public boolean isWriteable(final Class<?> clazz, final Type type, final Annotation[] annotation, final MediaType mediaType) {
+ return clazz == Message.class;
+ }
+
+ @Override
+ public long getSize(final Message message, final Class<?> clazz, final Type type, final Annotation[] annotation,
+ final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(final Message message, final Class<?> clazz, final Type type, final Annotation[] annotation,
+ final MediaType mediaType, final MultivaluedMap<String, Object> arg5, final OutputStream ostream)
+ throws IOException, WebApplicationException {
+ ostream.write(message.toString().getBytes(MessageUtils.getCharset(mediaType)));
+ }
+}
diff --git a/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/exceptions/CustomNotFoundException.java b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/exceptions/CustomNotFoundException.java
new file mode 100644
index 0000000..966a47e
--- /dev/null
+++ b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/exceptions/CustomNotFoundException.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jersey_ejb.exceptions;
+
+/**
+ * This exceptions will get mapped to a 404 response with the application exception mapper
+ * implemented by {@link NotFoundExceptionMapper} class.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class CustomNotFoundException extends Exception {
+
+}
diff --git a/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/exceptions/NotFoundExceptionMapper.java b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/exceptions/NotFoundExceptionMapper.java
new file mode 100644
index 0000000..d36593a
--- /dev/null
+++ b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/exceptions/NotFoundExceptionMapper.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jersey_ejb.exceptions;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * An exception mapper to return 404 responses when a {@link CustomNotFoundException} is thrown.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Provider
+public class NotFoundExceptionMapper implements ExceptionMapper<CustomNotFoundException> {
+
+ @Override
+ public Response toResponse(CustomNotFoundException exception) {
+ return Response.status(404).build();
+ }
+
+}
diff --git a/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/resources/MessageBoardResourceBean.java b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/resources/MessageBoardResourceBean.java
new file mode 100644
index 0000000..a55219e
--- /dev/null
+++ b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/resources/MessageBoardResourceBean.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jersey_ejb.resources;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import javax.ejb.EJB;
+import javax.ejb.Stateless;
+
+import org.glassfish.jersey.examples.jersey_ejb.entities.Message;
+import org.glassfish.jersey.examples.jersey_ejb.exceptions.CustomNotFoundException;
+
+/**
+ * A stateless EJB bean to handle REST requests to the messages resource.
+ * Messages are stored in the injected EJB singleton instance.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Stateless
+public class MessageBoardResourceBean {
+
+ @Context
+ private UriInfo ui;
+ @EJB
+ MessageHolderSingletonBean singleton;
+
+ /**
+ * Returns a list of all messages stored in the internal message holder.
+ */
+ @GET
+ public List<Message> getMessages() {
+ return singleton.getMessages();
+ }
+
+ @POST
+ public Response addMessage(String msg) throws URISyntaxException {
+ Message m = singleton.addMessage(msg);
+
+ URI msgURI = ui.getRequestUriBuilder().path(Integer.toString(m.getUniqueId())).build();
+
+ return Response.created(msgURI).build();
+ }
+
+ @Path("{msgNum}")
+ @GET
+ public Message getMessage(@PathParam("msgNum") int msgNum) {
+ Message m = singleton.getMessage(msgNum);
+
+ if (m == null) {
+ // This exception will be passed through to the JAX-RS runtime
+ // No other runtime exception will behave this way unless the
+ // exception is annotated with javax.ejb.ApplicationException
+ throw new NotFoundException();
+ }
+
+ return m;
+
+ }
+
+ @Path("{msgNum}")
+ @DELETE
+ public void deleteMessage(@PathParam("msgNum") int msgNum) throws CustomNotFoundException {
+ boolean deleted = singleton.deleteMessage(msgNum);
+
+ if (!deleted) {
+ // This exception will be mapped to a 404 response
+ throw new CustomNotFoundException();
+ }
+ }
+}
+
+
+
+
+
diff --git a/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/resources/MessageBoardRootResource.java b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/resources/MessageBoardRootResource.java
new file mode 100644
index 0000000..6fd665a
--- /dev/null
+++ b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/resources/MessageBoardRootResource.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jersey_ejb.resources;
+
+import javax.ejb.EJB;
+import javax.ejb.Stateless;
+import javax.ws.rs.Path;
+
+/**
+ * Message board root resource. The main message board resource
+ * gets injected as an EJB stateless bean
+ * and provided via a sub-resource locator for further processing.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Stateless
+@Path("/")
+public class MessageBoardRootResource {
+
+ @EJB MessageBoardResourceBean r;
+
+ @Path("messages")
+ public MessageBoardResourceBean getMessageBoardResourceBean() {
+ return r;
+ }
+}
+
diff --git a/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/resources/MessageHolderSingletonBean.java b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/resources/MessageHolderSingletonBean.java
new file mode 100644
index 0000000..8a700d6
--- /dev/null
+++ b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/resources/MessageHolderSingletonBean.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jersey_ejb.resources;
+
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.ejb.Singleton;
+
+import org.glassfish.jersey.examples.jersey_ejb.entities.Message;
+
+/**
+ * An EJB singleton to maintain all processed message beans.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Singleton
+public class MessageHolderSingletonBean {
+
+ private List<Message> list = new CopyOnWriteArrayList<Message>();
+ private int maxMessages = 10;
+
+ int currentId = 0;
+
+ public MessageHolderSingletonBean() {
+ // initial content
+ addMessage("msg0", new Date(0));
+ addMessage("msg1", new Date(1000));
+ addMessage("msg2", new Date(2000));
+ }
+
+ public List<Message> getMessages() {
+ List<Message> l = new LinkedList<Message>();
+
+ int index = 0;
+
+ while (index < list.size() && index < maxMessages) {
+ l.add(list.get(index));
+ index++;
+ }
+
+ return l;
+ }
+
+ private int getNewId() {
+ return currentId++;
+ }
+
+ public Message addMessage(String msg) {
+ return addMessage(msg, new Date());
+ }
+
+ private Message addMessage(String msg, Date date) {
+ Message m = new Message(date, msg, getNewId());
+
+ list.add(0, m);
+
+ return m;
+ }
+
+ public Message getMessage(int uniqueId) {
+ int index = 0;
+ Message m;
+
+ while (index < list.size()) {
+ if ((m = list.get(index)).getUniqueId() == uniqueId) {
+ return m;
+ }
+ index++;
+ }
+
+ return null;
+ }
+
+ public boolean deleteMessage(int uniqueId) {
+ int index = 0;
+
+ while (index < list.size()) {
+ if (list.get(index).getUniqueId() == uniqueId) {
+ list.remove(index);
+ return true;
+ }
+ index++;
+ }
+
+ return false;
+ }
+}
diff --git a/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/resources/MyApplication.java b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/resources/MyApplication.java
new file mode 100644
index 0000000..f21a779
--- /dev/null
+++ b/examples/jersey-ejb/src/main/java/org/glassfish/jersey/examples/jersey_ejb/resources/MyApplication.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jersey_ejb.resources;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.examples.jersey_ejb.entities.Message;
+import org.glassfish.jersey.examples.jersey_ejb.entities.MessageListWriter;
+import org.glassfish.jersey.examples.jersey_ejb.entities.MessageWriter;
+import org.glassfish.jersey.examples.jersey_ejb.exceptions.NotFoundExceptionMapper;
+
+/**
+ * Main application class.
+ *
+ * @author Jonathan Benoit
+ */
+@ApplicationPath("/app/*")
+public class MyApplication extends Application {
+ @Override
+ public Set<Class<?>> getClasses() {
+ final Set<Class<?>> classes = new HashSet<Class<?>>();
+ // register root resources/providers
+ classes.add(MessageBoardRootResource.class);
+ classes.add(MessageBoardResourceBean.class);
+ classes.add(MessageHolderSingletonBean.class);
+ classes.add(NotFoundExceptionMapper.class);
+ classes.add(MessageWriter.class);
+ classes.add(MessageListWriter.class);
+ classes.add(Message.class);
+ return classes;
+ }
+}
diff --git a/examples/jersey-ejb/src/main/webapp/index.html b/examples/jersey-ejb/src/main/webapp/index.html
new file mode 100644
index 0000000..cdcd4d3
--- /dev/null
+++ b/examples/jersey-ejb/src/main/webapp/index.html
@@ -0,0 +1,177 @@
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=MacRoman">
+
+ <style>
+ .created {color: green}
+ .uniqueId {color: red}
+ .message {color: black}
+ </style>
+ </head>
+
+ <body>
+
+ <script type="text/javascript">
+ function getXmlHttpRequest() {
+ try
+ {
+ // Firefox, Opera 8.0+, Safari
+ return new XMLHttpRequest();
+ }
+ catch (e)
+ {
+ // Internet Explorer
+ try
+ {
+ return new ActiveXObject("Msxml2.XMLHTTP");
+ }
+ catch (e)
+ {
+ try
+ {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ }
+ catch (e)
+ {
+ alert("Your browser does not support AJAX!");
+ return null;
+ }
+ }
+ }
+ }
+
+ function getMessages()
+ {
+ var xmlHttp = getXmlHttpRequest();
+ xmlHttp.onreadystatechange=function()
+ {
+ if(xmlHttp.readyState==4)
+ {
+ document.getElementById("query").innerHTML="GET app/messages";
+ document.getElementById("output").innerHTML=xmlHttp.responseText;
+ }
+ }
+ xmlHttp.open("GET","app/messages/",true);
+ xmlHttp.send(null);
+ }
+
+
+ function getMessage(index)
+ {
+ var xmlHttp = getXmlHttpRequest();
+ xmlHttp.onreadystatechange=function()
+ {
+ if(xmlHttp.readyState==4)
+ {
+ document.getElementById("query").innerHTML="GET app/messages/" + index;
+ if(xmlHttp.responseText.indexOf("<body>") != -1) {
+ s = xmlHttp.responseText.substring(xmlHttp.responseText.indexOf("<body>") + 6, xmlHttp.responseText.indexOf("</body>"))
+ document.getElementById("output").innerHTML=s;
+ } else {
+ document.getElementById("output").innerHTML=xmlHttp.responseText;
+ }
+
+ }
+ }
+ xmlHttp.open("GET","app/messages/" + index,true);
+ xmlHttp.send(null);
+ }
+
+ function deleteMessage(index)
+ {
+ var xmlHttp = getXmlHttpRequest();
+ xmlHttp.onreadystatechange=function()
+ {
+ if(xmlHttp.readyState==4)
+ {
+ document.getElementById("query").innerHTML="DELETE app/messages/" + index;
+ if(xmlHttp.responseText.indexOf("<body>") != -1) {
+ s = xmlHttp.responseText.substring(xmlHttp.responseText.indexOf("<body>") + 6, xmlHttp.responseText.indexOf("</body>"))
+ document.getElementById("output").innerHTML=s;
+ } else {
+ document.getElementById("output").innerHTML=xmlHttp.responseText;
+ }
+ }
+ }
+ xmlHttp.open("DELETE","app/messages/" + index,true);
+ xmlHttp.send(null);
+ }
+
+ function addMessage(message)
+ {
+ var xmlHttp = getXmlHttpRequest();
+ xmlHttp.onreadystatechange=function()
+ {
+ if(xmlHttp.readyState==4)
+ {
+ document.getElementById("query").innerHTML="POST app/messages";
+ document.getElementById("output").innerHTML="";
+ }
+ }
+ xmlHttp.open("POST","app/messages/",true);
+ xmlHttp.send(message);
+ }
+
+
+ </script>
+
+
+ <h1>MessageBoard sample</h1>
+
+ <form name="form0">
+
+ <button type="button" name="GET0" onclick="getMessages()">LIST ALL MESSAGES</button>
+
+ </form>
+
+
+ <form name="form1">
+ Message id#: <input style="width: 4em" type="text" name="messageNumber" />
+
+<button type="button" name="GET1" onclick="getMessage(document.form1.messageNumber.value)">GET MESSAGE</button>
+
+</form>
+
+<form name="form2">
+ Message id#: <input style="width: 4em" type="text" name="messageNumber" />
+
+<button type="button" name="GET2" onclick="deleteMessage(document.form2.messageNumber.value)">DELETE MESSAGE</button>
+
+</form>
+
+<form name="form3">
+ Message: <input type="test" name="messageText" />
+
+ <button type="button" name="GET2" onclick="addMessage(document.form3.messageText.value)">ADD MESSAGE</button>
+
+</form>
+
+
+<h2>MessageBoard</h2>
+ <h3>query: </h3>
+<div id="query" style="font-weight: bold"></div>
+ <h3>result</h3>
+<div id="output" style="border: 1px solid black; color: black; height: 18em; width: 100%"></div>
+
+
+<script type="text/javascript">
+ getMessages();
+</script>
+
+
+</body>
+</html>
diff --git a/examples/jersey-ejb/src/test/java/org/glassfish/jersey/examples/jersey_ejb/test/MessageBoardTest.java b/examples/jersey-ejb/src/test/java/org/glassfish/jersey/examples/jersey_ejb/test/MessageBoardTest.java
new file mode 100644
index 0000000..d5503a1
--- /dev/null
+++ b/examples/jersey-ejb/src/test/java/org/glassfish/jersey/examples/jersey_ejb/test/MessageBoardTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jersey_ejb.test;
+
+import java.net.URI;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.examples.jersey_ejb.resources.MyApplication;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Basic test for adding/removing messages.
+ *
+ * Tests currently disabled in pom.xml file.
+ *
+ * To test the app, mvn clean package and asadmin deploy target/jersey-ejb
+ * and then run the tests using extenrnal test container factory:
+ * mvn -Prun-external-tests test
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class MessageBoardTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ return new MyApplication();
+ }
+
+ @Override
+ protected URI getBaseUri() {
+ return UriBuilder.fromUri(super.getBaseUri()).path("jersey-ejb").build();
+ }
+
+ /**
+ * Regression test for JERSEY-2541.
+ */
+ @Test
+ public void testListMessages() {
+ Response response = target().path("app/messages").request(MediaType.TEXT_HTML).get();
+
+ assertEquals(
+ String.format("Response status should be 200. Current value is %d.", response.getStatus()),
+ 200, response.getStatus());
+ }
+
+ @Test
+ public void testAddMessage() {
+
+ Response response = target().path("app/messages").request(MediaType.TEXT_PLAIN)
+ .post(Entity.entity("hello world!", MediaType.TEXT_PLAIN));
+
+ assertEquals("Response status should be CREATED. Current value is \"" + response.getStatus() + "\"",
+ Response.Status.CREATED.getStatusCode(), response.getStatus());
+
+ client().target(response.getLocation()).request().delete(); // remove added message
+ }
+
+ @Test
+ public void testDeleteMessage() {
+
+ URI u = null;
+
+ Response response = target().path("app/messages").request().post(Entity.entity("toDelete", MediaType.TEXT_PLAIN));
+ if (response.getStatus() == Response.Status.CREATED.getStatusCode()) {
+ u = response.getLocation();
+ } else {
+ fail();
+ }
+
+ String s = client().target(u).request().get(String.class);
+
+ assertTrue(s.contains("toDelete"));
+
+ Response firstDeleteResponse = client().target(u).request().delete();
+ final int successfulDeleteResponseStatus = firstDeleteResponse.getStatus();
+ assertTrue("First DELETE request should return with a 2xx status code",
+ (200 <= successfulDeleteResponseStatus) && (successfulDeleteResponseStatus < 300));
+
+ Response nonExistentGetResponse = client().target(u).request().get();
+ assertEquals("GET request to a non existent resource should return 404", 404, nonExistentGetResponse.getStatus());
+
+ Response nonExistentDeleteResponse = client().target(u).request().delete();
+ assertEquals("DELETE request to a non existent resource should return 404", 404, nonExistentDeleteResponse.getStatus());
+ }
+
+}
diff --git a/examples/json-binding-webapp/README.MD b/examples/json-binding-webapp/README.MD
new file mode 100644
index 0000000..e5612bf
--- /dev/null
+++ b/examples/json-binding-webapp/README.MD
@@ -0,0 +1,43 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+JSONB Example
+=============
+
+This example demonstrates how to use JSONB with resource classes.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+--------------------------------------- | ------------------------ | --------------
+**_/jsonb/cats/one_** | JsonbResource | GET
+**_/jsonb/cats/all_** | JsonbResource | GET
+**_/jsonb/cats/add_** | JsonbResource | POST
+**_/jsonb/cats/addAll_** | JsonbResource | POST
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container. You can access the application at:
+
+- <http://localhost:8080/json-binding-webapp/cats/one>
+- <http://localhost:8080/json-binding-webapp/cats/all>
+
+or you can post an xml entity:
+
+> curl -v -X POST http://localhost:8080/json-binding-webapp/cats/add -H "Content-Type:application/json" -d '
+> {"color":"white","sort":"maine coon","catName":"Darwin", "domesticated":"true"}'
+
+You can also use the built war file to deploy the app to a container of your choice.
\ No newline at end of file
diff --git a/examples/json-binding-webapp/pom.xml b/examples/json-binding-webapp/pom.xml
new file mode 100644
index 0000000..5eef5aa
--- /dev/null
+++ b/examples/json-binding-webapp/pom.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>json-binding-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-json-binding-webapp</name>
+
+ <description>Jersey JSON Binding example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-binding</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.jsonb.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/json-binding-webapp/src/main/java/org/glassfish/jersey/examples/jsonb/App.java b/examples/json-binding-webapp/src/main/java/org/glassfish/jersey/examples/jsonb/App.java
new file mode 100644
index 0000000..39d18e7
--- /dev/null
+++ b/examples/json-binding-webapp/src/main/java/org/glassfish/jersey/examples/jsonb/App.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonb;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Jersey JSON-B example standalone application.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/json-binding-webapp/");
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("JSON-B (json-binding) Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, new JsonbApplication(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(server::shutdownNow));
+ server.start();
+
+ System.out.println(
+ String.format("Application started.%nTry out %s%nStop the application using CTRL+C", BASE_URI));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+}
diff --git a/examples/json-binding-webapp/src/main/java/org/glassfish/jersey/examples/jsonb/Cat.java b/examples/json-binding-webapp/src/main/java/org/glassfish/jersey/examples/jsonb/Cat.java
new file mode 100644
index 0000000..720a63e
--- /dev/null
+++ b/examples/json-binding-webapp/src/main/java/org/glassfish/jersey/examples/jsonb/Cat.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonb;
+
+import javax.json.bind.annotation.JsonbProperty;
+import javax.json.bind.annotation.JsonbPropertyOrder;
+
+/**
+ * Example cat POJO for JSONB (un)marshalling.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@JsonbPropertyOrder({"color", "sort", "name", "domesticated"})
+public class Cat {
+ @JsonbProperty("catName")
+ private String name;
+ private String sort;
+ private String color;
+ private boolean domesticated;
+
+ // json-b needs the default constructor
+ public Cat() {
+ super();
+ }
+
+ public Cat(String name, String sort, String color, boolean domesticated) {
+ this.name = name;
+ this.sort = sort;
+ this.color = color;
+ this.domesticated = domesticated;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Cat setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @JsonbProperty("catSort")
+ public String getSort() {
+ return sort;
+ }
+
+ public Cat setSort(String sort) {
+ this.sort = sort;
+ return this;
+ }
+
+ public String getColor() {
+ return color;
+ }
+
+ public Cat setColor(String color) {
+ this.color = color;
+ return this;
+ }
+
+ public boolean isDomesticated() {
+ return domesticated;
+ }
+
+ public Cat setDomesticated(boolean domesticated) {
+ this.domesticated = domesticated;
+ return this;
+ }
+}
diff --git a/examples/json-binding-webapp/src/main/java/org/glassfish/jersey/examples/jsonb/JsonbApplication.java b/examples/json-binding-webapp/src/main/java/org/glassfish/jersey/examples/jsonb/JsonbApplication.java
new file mode 100644
index 0000000..c89b53d
--- /dev/null
+++ b/examples/json-binding-webapp/src/main/java/org/glassfish/jersey/examples/jsonb/JsonbApplication.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonb;
+
+import javax.ws.rs.ApplicationPath;
+
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * Jersey JSON-B example application.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@ApplicationPath("/")
+public class JsonbApplication extends ResourceConfig {
+ public JsonbApplication() {
+ register(JsonbResource.class);
+ }
+}
diff --git a/examples/json-binding-webapp/src/main/java/org/glassfish/jersey/examples/jsonb/JsonbResource.java b/examples/json-binding-webapp/src/main/java/org/glassfish/jersey/examples/jsonb/JsonbResource.java
new file mode 100644
index 0000000..74d6d9f
--- /dev/null
+++ b/examples/json-binding-webapp/src/main/java/org/glassfish/jersey/examples/jsonb/JsonbResource.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonb;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+
+/**
+ * An example resource utilizing JSONB.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@Path("cats")
+@Consumes("application/json")
+public class JsonbResource {
+
+ private static List<Cat> cats = new ArrayList<>();
+
+ static {
+ cats.add(new Cat("Rosa", "semi-british", "tabby", true));
+ cats.add(new Cat("Alfred", "semi-british", "ginger", true));
+ cats.add(new Cat("Mishan", "british blue", "blue/silver", true));
+ cats.add(new Cat("Costa", "common cat", "stracciatella", true));
+ }
+
+ @Path("one")
+ @GET
+ @Produces("application/json")
+ public Cat getCat() {
+ return cats.get((int) (Math.round(Math.random() * 3)));
+ }
+
+ @Path("all")
+ @GET
+ @Produces("application/json")
+ public List<Cat> getAll() {
+ return cats;
+ }
+
+ @Path("schroedinger")
+ @GET
+ public String check() {
+ return "The cat is 9x alive!";
+ }
+
+ @Path("add")
+ @POST
+ public Response createCat(Cat cat) {
+ System.out.println("Creating cat.");
+ cats.add(cat);
+ return Response.ok().build();
+ }
+
+ @Path("addAll")
+ @POST
+ public Response createMultiple(List<Cat> addedCats) {
+ cats.addAll(addedCats);
+ return Response.ok().build();
+ }
+
+}
diff --git a/examples/json-jackson/README.MD b/examples/json-jackson/README.MD
new file mode 100644
index 0000000..6fe56aa
--- /dev/null
+++ b/examples/json-jackson/README.MD
@@ -0,0 +1,91 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Jackson JAX-RS JSON Provider Example
+====================================
+
+This example demonstrates how to produce/consume JSON representations
+from Java objects. This applies not only to JAXB beans, as shown in the
+`json-from-jaxb` example but also to ordinary, un-annotated, POJOs.
+
+This example hosts three simple read-only resources: One provides an
+example of using a Jackson JSON provider (registered by the feature
+`JacksonFeature` in the `MyApplication` class) instead of using JAXB
+(Object->JAXB->JSON) which has some limitations (e.g. empty arrays
+in JAXB beans). For this resource the JSON representation is produced by
+the Jackson JAX-RS provider, while the XML representation is generated
+by JAXB as usual. The second web resource is based on a simple
+un-annotated POJO and provides only JSON-based representations: JSON and
+JSON with padding (JSONP). The third resource depicts how Jackson and
+JAXB annotations could be mixed together within a single POJO.
+
+Contents
+--------
+
+The "empty array" web resource is implemented by the `org.glassfish.jersey.examples.jackson.EmptyArrayResource` class.
+
+The "non JAXB" web resource is implemented by the `org.glassfish.jersey.examples.jackson.NonJaxbBeanResource` class.
+
+Both resources use the default Jackson mapper configuration to serialize JSON
+data out and depicts the corner cases, where the Jersey internal, StAX
+based, JSON processing can not be utilized.
+
+The `org.glassfish.jersey.examples.jackson.CombinedAnnotationResource`
+class is used to show how JAXB and Jackson annotations could be combined
+together in one Java bean for JSON serialization. See the
+
+`org.glassfish.jersey.examples.jackson.MyObjectMapperProvider` class
+where Jackson specific options are used to set up the desired JSON
+serialization configuration.
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP method
+---------------------------- | ---------------------------- | -------------
+**_/emptyArrayResource_** | EmptyArrayResource | GET
+**_/nonJaxbResource_** | NonJaxbBeanResource | GET
+**_/combinedAnnotations_** | CombinedAnnotationResource | GET
+
+To use Jackson specific configuration options, one can implement a
+`ContextResolver<ObjectMapper>` provider. For an example of such an
+implementation, see the `MyObjectMapperProvider` class.
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using[Grizzly](http://grizzly.java.net/)
+
+A [WADL description](http://wadl.java.net/#spec) may be accessed at the URL:
+
+- <http://localhost:8080/jackson/application.wadl>
+
+The three resources are available at
+
+- <http://localhost:8080/jackson/emptyArrayResource>
+- <http://localhost:8080/jackson/nonJaxbResource>
+- <http://localhost:8080/jackson/combinedAnnotations>
+
+To easily obtain the different output types available,
+[cURL](http://curl.haxx.se/) can be used as follows:
+
+Obtain the JSON output of EmptyArrayResource (use
+`-HAccept:application/xml` for XML output):
+
+> curl -HAccept:application/json http://localhost:8080/jackson/emptyArrayResource
+
+Obtain the JSON output of NonJaxbBeanResource (use `-HAccept:application/javascript` for JSONP output):
+
+> curl -HAccept:application/json http://localhost:8080/jackson/nonJaxbResource
+
+Obtain the JSON output of CombinedAnnotationResource:
+
+> curl -HAccept:application/json http://localhost:8080/jackson/combinedAnnotations
diff --git a/examples/json-jackson/pom.xml b/examples/json-jackson/pom.xml
new file mode 100644
index 0000000..16d0f6d
--- /dev/null
+++ b/examples/json-jackson/pom.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>json-jackson</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-json-jackson</name>
+
+ <description>Jersey JSON with Jackson example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jackson</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-util</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.jackson.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/json-jackson/src/main/assembly/src.xml b/examples/json-jackson/src/main/assembly/src.xml
new file mode 100644
index 0000000..a030433
--- /dev/null
+++ b/examples/json-jackson/src/main/assembly/src.xml
@@ -0,0 +1,28 @@
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<assembly>
+ <id>project</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <fileSets>
+ <fileSet>
+ <directory>.</directory>
+ <outputDirectory></outputDirectory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ <excludes>
+ <exclude>**/target/**</exclude>
+ </excludes>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/App.java b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/App.java
new file mode 100644
index 0000000..11bb1c0
--- /dev/null
+++ b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/App.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Utility class which creates {@link MyApplication} instances and provides support
+ * for running this sample from command line.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/jackson/");
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("JSON with Jackson Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, createApp(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.%nStop the application using CTRL+C"));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ public static ResourceConfig createApp() {
+ return new MyApplication();
+ }
+}
diff --git a/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/CombinedAnnotationBean.java b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/CombinedAnnotationBean.java
new file mode 100644
index 0000000..1852773
--- /dev/null
+++ b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/CombinedAnnotationBean.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * TODO javadoc.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@XmlRootElement(name = "account")
+public class CombinedAnnotationBean {
+
+ @JsonProperty("value")
+ int x;
+
+ public CombinedAnnotationBean(int x) {
+ this.x = x;
+ }
+
+ public CombinedAnnotationBean() {
+ this(15);
+ }
+}
diff --git a/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/CombinedAnnotationResource.java b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/CombinedAnnotationResource.java
new file mode 100644
index 0000000..4bdead6
--- /dev/null
+++ b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/CombinedAnnotationResource.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * TODO javadoc.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("combinedAnnotations")
+public class CombinedAnnotationResource {
+
+ @Produces(MediaType.APPLICATION_JSON)
+ @GET
+ public CombinedAnnotationBean getAccount() {
+ return new CombinedAnnotationBean(12);
+ }
+}
diff --git a/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/DummyBean.java b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/DummyBean.java
new file mode 100644
index 0000000..e505e9d
--- /dev/null
+++ b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/DummyBean.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson;
+
+/**
+ * Testing bean for ExceptionMappingTestResource.
+ *
+ */
+public class DummyBean {
+ private int key;
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public int getKey() {
+ return key;
+ }
+
+ public void setKey(int key) {
+ this.key = key;
+ }
+
+}
diff --git a/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/EmptyArrayBean.java b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/EmptyArrayBean.java
new file mode 100644
index 0000000..332ae84
--- /dev/null
+++ b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/EmptyArrayBean.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * TODO javadoc.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@XmlRootElement
+public class EmptyArrayBean {
+
+ public String[] emtpyArray = new String[0];
+}
diff --git a/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/EmptyArrayResource.java b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/EmptyArrayResource.java
new file mode 100644
index 0000000..0f4abf5
--- /dev/null
+++ b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/EmptyArrayResource.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * TODO javadoc.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("/emptyArrayResource")
+public class EmptyArrayResource {
+
+ // the resource JSON representation will be serialized by Jackson JAX-RS provider,
+ // while the XML will still be generated by JAXB
+ @GET
+ @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+ public EmptyArrayBean getIt() {
+ return new EmptyArrayBean();
+ }
+}
diff --git a/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/ExceptionMappingTestResource.java b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/ExceptionMappingTestResource.java
new file mode 100644
index 0000000..4afef0e
--- /dev/null
+++ b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/ExceptionMappingTestResource.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Testing bean that accepts JSON for the PUT method.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@Path("parseExceptionTest")
+public class ExceptionMappingTestResource {
+
+ @Consumes(MediaType.APPLICATION_JSON)
+ @PUT
+ public DummyBean getAccount(DummyBean bean) {
+ return bean;
+ }
+}
diff --git a/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/MyApplication.java b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/MyApplication.java
new file mode 100644
index 0000000..aa4c966
--- /dev/null
+++ b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/MyApplication.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson;
+
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * {@link javax.ws.rs.core.Application} descendant.
+ *
+ * Used to set resource and providers classes.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class MyApplication extends ResourceConfig {
+ public MyApplication() {
+ super(
+ EmptyArrayResource.class,
+ NonJaxbBeanResource.class,
+ CombinedAnnotationResource.class,
+ // register Jackson ObjectMapper resolver
+ MyObjectMapperProvider.class,
+ ExceptionMappingTestResource.class,
+ JacksonFeature.class
+ );
+ }
+}
diff --git a/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/MyObjectMapperProvider.java b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/MyObjectMapperProvider.java
new file mode 100644
index 0000000..df9658b
--- /dev/null
+++ b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/MyObjectMapperProvider.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson;
+
+import javax.ws.rs.ext.ContextResolver;
+import javax.ws.rs.ext.Provider;
+
+import com.fasterxml.jackson.databind.AnnotationIntrospector;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
+
+/**
+ * TODO javadoc.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Provider
+public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
+
+ final ObjectMapper defaultObjectMapper;
+ final ObjectMapper combinedObjectMapper;
+
+ public MyObjectMapperProvider() {
+ defaultObjectMapper = createDefaultMapper();
+ combinedObjectMapper = createCombinedObjectMapper();
+ }
+
+ @Override
+ public ObjectMapper getContext(final Class<?> type) {
+
+ if (type == CombinedAnnotationBean.class) {
+ return combinedObjectMapper;
+ } else {
+ return defaultObjectMapper;
+ }
+ }
+
+ private static ObjectMapper createCombinedObjectMapper() {
+ return new ObjectMapper()
+ .configure(SerializationFeature.WRAP_ROOT_VALUE, true)
+ .configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true)
+ .setAnnotationIntrospector(createJaxbJacksonAnnotationIntrospector());
+ }
+
+ private static ObjectMapper createDefaultMapper() {
+ final ObjectMapper result = new ObjectMapper();
+ result.enable(SerializationFeature.INDENT_OUTPUT);
+
+ return result;
+ }
+
+ private static AnnotationIntrospector createJaxbJacksonAnnotationIntrospector() {
+
+ final AnnotationIntrospector jaxbIntrospector = new JaxbAnnotationIntrospector(TypeFactory.defaultInstance());
+ final AnnotationIntrospector jacksonIntrospector = new JacksonAnnotationIntrospector();
+
+ return AnnotationIntrospector.pair(jacksonIntrospector, jaxbIntrospector);
+ }
+}
diff --git a/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/NonJaxbBean.java b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/NonJaxbBean.java
new file mode 100644
index 0000000..8446f3b
--- /dev/null
+++ b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/NonJaxbBean.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson;
+
+/**
+ * TODO javadoc.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class NonJaxbBean {
+
+ private String name = "non-JAXB-bean";
+ private String description = "I am not a JAXB bean, just an unannotated POJO";
+ private int[] array = {1, 1, 2, 3, 5, 8, 13, 21};
+
+ public int[] getArray() {
+ return array;
+ }
+
+ public void setArray(int[] array) {
+ this.array = array;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/NonJaxbBeanResource.java b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/NonJaxbBeanResource.java
new file mode 100644
index 0000000..88e01e7
--- /dev/null
+++ b/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/NonJaxbBeanResource.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.server.JSONP;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("/nonJaxbResource")
+public class NonJaxbBeanResource {
+
+ @GET
+ @JSONP
+ @Produces({"application/javascript", MediaType.APPLICATION_JSON})
+ public NonJaxbBean getSimpleBeanJSONP() {
+ return new NonJaxbBean();
+ }
+}
diff --git a/examples/json-jackson/src/test/java/org/glassfish/jersey/examples/jackson/JacksonTest.java b/examples/json-jackson/src/test/java/org/glassfish/jersey/examples/jackson/JacksonTest.java
new file mode 100644
index 0000000..64e662e
--- /dev/null
+++ b/examples/json-jackson/src/test/java/org/glassfish/jersey/examples/jackson/JacksonTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.message.internal.MediaTypes;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.glassfish.jersey.test.util.runner.ConcurrentRunner;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.runner.RunWith;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@RunWith(ConcurrentRunner.class)
+public class JacksonTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ enable(TestProperties.DUMP_ENTITY);
+
+ return App.createApp();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(new JacksonFeature()).register(MyObjectMapperProvider.class);
+ }
+
+ @Test
+ public void testEmptyArrayPresent() {
+ WebTarget target = target();
+ String responseMsg = target.path("emptyArrayResource").request(MediaType.APPLICATION_JSON).get(String.class);
+ assertTrue(responseMsg.replaceAll("[ \t]*", "").contains("[]"));
+ }
+
+ @Test
+ public void testJSONPPresent() {
+ WebTarget target = target();
+ String responseMsg = target.path("nonJaxbResource").request("application/javascript").get(String.class);
+ assertTrue(responseMsg.startsWith("callback("));
+ }
+
+ @Test
+ public void testJSONDoesNotReflectJSONPWrapper() {
+ WebTarget target = target();
+ String responseMsg = target.path("nonJaxbResource").request("application/json").get(String.class);
+ assertTrue(!responseMsg.contains("jsonSource"));
+ }
+
+ @Test
+ public void testCombinedAnnotationResource() {
+ WebTarget target = target();
+ String responseMsg = target.path("combinedAnnotations").request("application/json").get(String.class);
+ assertTrue(responseMsg.contains("account") && responseMsg.contains("value"));
+ }
+
+ @Test
+ public void testEmptyArrayBean() {
+ WebTarget target = target();
+ EmptyArrayBean responseMsg = target.path("emptyArrayResource").request(MediaType.APPLICATION_JSON)
+ .get(EmptyArrayBean.class);
+ assertNotNull(responseMsg);
+ }
+
+ @Test
+ public void testCombinedAnnotationBean() {
+ WebTarget target = target();
+ CombinedAnnotationBean responseMsg = target.path("combinedAnnotations").request("application/json")
+ .get(CombinedAnnotationBean.class);
+ assertNotNull(responseMsg);
+ }
+
+ @Test
+ @Ignore
+ // TODO un-ignore once a JSON reader for "application/javascript" is supported
+ public void testJSONPBean() {
+ WebTarget target = target();
+ NonJaxbBean responseMsg = target.path("nonJaxbResource").request("application/javascript").get(NonJaxbBean.class);
+ assertNotNull(responseMsg);
+ }
+
+ /**
+ * Test if a WADL document is available at the relative path
+ * "application.wadl".
+ * <p/>
+ */
+ @Test
+ public void testApplicationWadl() {
+ WebTarget target = target();
+ String serviceWadl = target.path("application.wadl").request(MediaTypes.WADL_TYPE).get(String.class);
+
+ assertTrue(serviceWadl.length() > 0);
+ }
+
+ /**
+ * Test, that in case of malformed JSON, the jackson exception mappers will be used and the response will be
+ * 400 - bad request instead of 500 - server error
+ */
+ @Test
+ public void testExceptionMapping() {
+ enable(TestProperties.LOG_TRAFFIC);
+ // create a request with invalid json string to cause an exception in Jackson
+ Response response = target().path("parseExceptionTest").request("application/json")
+ .put(Entity.entity("Malformed json string.", MediaType.valueOf("application/json")));
+
+ assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+}
diff --git a/examples/json-jackson1/README.MD b/examples/json-jackson1/README.MD
new file mode 100644
index 0000000..feb7fa2
--- /dev/null
+++ b/examples/json-jackson1/README.MD
@@ -0,0 +1,91 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Jackson (1.x) JAX-RS JSON Provider Example
+==========================================
+
+This example demonstrates how to produce/consume JSON representations
+from Java objects. This applies not only to JAXB beans, as shown in the
+`json-from-jaxb` example but also to ordinary, un-annotated, POJOs.
+
+This example hosts three simple read-only resources: One provides an
+example of using a Jackson JSON provider (registered by the feature
+`Jackson1Feature` in the `MyApplication` class) instead of using JAXB
+(Object->JAXB->JSON) which has some limitations (e.g. empty arrays
+in JAXB beans). For this resource the JSON representation is produced by
+the Jackson JAX-RS provider, while the XML representation is generated
+by JAXB as usual. The second web resource is based on a simple
+un-annotated POJO and provides only JSON-based representations: JSON and
+JSON with padding (JSONP). The third resource depicts how Jackson and
+JAXB annotations could be mixed together within a single POJO.
+
+Contents
+--------
+
+The "empty array" web resource is implemented by the
+`org.glassfish.jersey.examples.jackson1.EmptyArrayResource` class.
+
+The "non JAXB" web resource is implemented by the
+`org.glassfish.jersey.examples.jackson1.NonJaxbBeanResource` class.
+
+Both resources use the default Jackson mapper configuration to serialize JSON
+data out and depicts the corner cases, where the Jersey internal, StAX
+based, JSON processing can not be utilized.
+
+The `org.glassfish.jersey.examples.jackson1.CombinedAnnotationResource`
+class is used to show how JAXB and Jackson annotations could be combined
+together in one Java bean for JSON serialization.
+
+See the `org.glassfish.jersey.examples.jackson1.MyObjectMapperProvider` class
+where Jackson specific options are used to set up the desired JSON
+serialization configuration.
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP method
+---------------------------- | ---------------------------- | -------------
+**_/emptyArrayResource_** | EmptyArrayResource | GET
+**_/nonJaxbResource_** | NonJaxbBeanResource | GET
+**_/combinedAnnotations_** | CombinedAnnotationResource | GET
+
+To use Jackson specific configuration options, one can implement a
+`ContextResolver<ObjectMapper>` provider. For an example of such an
+implementation, see the `MyObjectMapperProvider` class.
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/)
+
+A [WADL description](http://wadl.java.net/#spec) may be accessed at the URL:
+
+- `http://localhost:8080/jackson1/application.wadl`
+
+The three resources are available at
+
+- <http://localhost:8080/jackson1/emptyArrayResource>
+- <http://localhost:8080/jackson1/nonJaxbResource>
+- <http://localhost:8080/jackson1/combinedAnnotations>
+
+To easily obtain the different output types available, [cURL](http://curl.haxx.se/) can be used as follows:
+
+Obtain the JSON output of EmptyArrayResource (use `-HAccept:application/xml` for XML output):
+
+> curl -HAccept:application/json http://localhost:8080/jackson1/emptyArrayResource
+
+Obtain the JSON output of NonJaxbBeanResource (use `-HAccept:application/javascript` for JSONP output):
+
+> curl -HAccept:application/json http://localhost:8080/jackson1/nonJaxbResource
+
+Obtain the JSON output of CombinedAnnotationResource:
+
+> curl -HAccept:application/json http://localhost:8080/jackson1/combinedAnnotations
diff --git a/examples/json-jackson1/pom.xml b/examples/json-jackson1/pom.xml
new file mode 100644
index 0000000..b68c645
--- /dev/null
+++ b/examples/json-jackson1/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>json-jackson1</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-json-jackson1</name>
+
+ <description>Jersey JSON with Jackson 1.x example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jackson1</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.jackson1.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/json-jackson1/src/main/assembly/src.xml b/examples/json-jackson1/src/main/assembly/src.xml
new file mode 100644
index 0000000..a030433
--- /dev/null
+++ b/examples/json-jackson1/src/main/assembly/src.xml
@@ -0,0 +1,28 @@
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<assembly>
+ <id>project</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <fileSets>
+ <fileSet>
+ <directory>.</directory>
+ <outputDirectory></outputDirectory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ <excludes>
+ <exclude>**/target/**</exclude>
+ </excludes>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/App.java b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/App.java
new file mode 100644
index 0000000..0cd8dfb
--- /dev/null
+++ b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/App.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson1;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Utility class which creates {@link MyApplication} instances and provides support
+ * for running this sample from command line.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/jackson1/");
+
+ public static void main(final String[] args) {
+ try {
+ System.out.println("JSON with Jackson Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, createApp(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.%nStop the application using CTRL+C"));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ public static ResourceConfig createApp() {
+ return new MyApplication();
+ }
+}
diff --git a/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/CombinedAnnotationBean.java b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/CombinedAnnotationBean.java
new file mode 100644
index 0000000..68ad3b2
--- /dev/null
+++ b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/CombinedAnnotationBean.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson1;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@XmlRootElement(name = "account")
+public class CombinedAnnotationBean {
+
+ @JsonProperty("value")
+ int x;
+
+ public CombinedAnnotationBean(final int x) {
+ this.x = x;
+ }
+
+ public CombinedAnnotationBean() {
+ this(15);
+ }
+}
diff --git a/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/CombinedAnnotationResource.java b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/CombinedAnnotationResource.java
new file mode 100644
index 0000000..841d930
--- /dev/null
+++ b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/CombinedAnnotationResource.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson1;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("combinedAnnotations")
+public class CombinedAnnotationResource {
+
+ @Produces(MediaType.APPLICATION_JSON)
+ @GET
+ public CombinedAnnotationBean getAccount() {
+ return new CombinedAnnotationBean(12);
+ }
+}
diff --git a/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/DummyBean.java b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/DummyBean.java
new file mode 100644
index 0000000..c3e66e1
--- /dev/null
+++ b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/DummyBean.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson1;
+
+/**
+ * Testing bean for ExceptionMappingTestResource.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class DummyBean {
+
+ private int key;
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(final String value) {
+ this.value = value;
+ }
+
+ public int getKey() {
+ return key;
+ }
+
+ public void setKey(final int key) {
+ this.key = key;
+ }
+
+}
diff --git a/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/EmptyArrayBean.java b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/EmptyArrayBean.java
new file mode 100644
index 0000000..f6c1164
--- /dev/null
+++ b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/EmptyArrayBean.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson1;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@XmlRootElement
+public class EmptyArrayBean {
+
+ public String[] emtpyArray = new String[0];
+}
diff --git a/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/EmptyArrayResource.java b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/EmptyArrayResource.java
new file mode 100644
index 0000000..53ac9f0
--- /dev/null
+++ b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/EmptyArrayResource.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson1;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("/emptyArrayResource")
+public class EmptyArrayResource {
+
+ // the resource JSON representation will be serialized by Jackson JAX-RS provider,
+ // while the XML will still be generated by JAXB
+ @GET
+ @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+ public EmptyArrayBean getIt() {
+ return new EmptyArrayBean();
+ }
+}
diff --git a/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/ExceptionMappingTestResource.java b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/ExceptionMappingTestResource.java
new file mode 100644
index 0000000..542b70c
--- /dev/null
+++ b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/ExceptionMappingTestResource.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson1;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Testing bean that accepts JSON for the PUT method.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@Path("parseExceptionTest")
+public class ExceptionMappingTestResource {
+
+ @Consumes(MediaType.APPLICATION_JSON)
+ @PUT
+ public DummyBean getAccount(final DummyBean bean) {
+ return bean;
+ }
+}
diff --git a/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/MyApplication.java b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/MyApplication.java
new file mode 100644
index 0000000..1967697
--- /dev/null
+++ b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/MyApplication.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson1;
+
+import org.glassfish.jersey.jackson1.Jackson1Feature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * {@link javax.ws.rs.core.Application} descendant.
+ *
+ * Used to set resource and providers classes.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class MyApplication extends ResourceConfig {
+
+ public MyApplication() {
+ super(
+ EmptyArrayResource.class,
+ NonJaxbBeanResource.class,
+ CombinedAnnotationResource.class,
+ // register Jackson ObjectMapper resolver
+ MyObjectMapperProvider.class,
+ ExceptionMappingTestResource.class,
+ Jackson1Feature.class
+ );
+ }
+}
diff --git a/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/MyObjectMapperProvider.java b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/MyObjectMapperProvider.java
new file mode 100644
index 0000000..7e6996f
--- /dev/null
+++ b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/MyObjectMapperProvider.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson1;
+
+import javax.ws.rs.ext.ContextResolver;
+import javax.ws.rs.ext.Provider;
+
+import org.codehaus.jackson.map.AnnotationIntrospector;
+import org.codehaus.jackson.map.AnnotationIntrospector.Pair;
+import org.codehaus.jackson.map.DeserializationConfig;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.codehaus.jackson.map.SerializationConfig.Feature;
+import org.codehaus.jackson.map.introspect.JacksonAnnotationIntrospector;
+import org.codehaus.jackson.xc.JaxbAnnotationIntrospector;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Provider
+public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
+
+ final ObjectMapper defaultObjectMapper;
+ final ObjectMapper combinedObjectMapper;
+
+ public MyObjectMapperProvider() {
+ defaultObjectMapper = createDefaultMapper();
+ combinedObjectMapper = createCombinedObjectMapper();
+ }
+
+ @Override
+ public ObjectMapper getContext(final Class<?> type) {
+
+ if (type == CombinedAnnotationBean.class) {
+ return combinedObjectMapper;
+ } else {
+ return defaultObjectMapper;
+ }
+ }
+
+ private static ObjectMapper createCombinedObjectMapper() {
+ final Pair combinedIntrospector = createJaxbJacksonAnnotationIntrospector();
+ final ObjectMapper result = new ObjectMapper();
+ result.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
+ result.configure(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE, true);
+ result.setDeserializationConfig(result.getDeserializationConfig().withAnnotationIntrospector(combinedIntrospector));
+ result.setSerializationConfig(result.getSerializationConfig().withAnnotationIntrospector(combinedIntrospector));
+
+ return result;
+ }
+
+ private static ObjectMapper createDefaultMapper() {
+
+ final ObjectMapper result = new ObjectMapper();
+ result.configure(Feature.INDENT_OUTPUT, true);
+
+ return result;
+ }
+
+ private static Pair createJaxbJacksonAnnotationIntrospector() {
+
+ final AnnotationIntrospector jaxbIntrospector = new JaxbAnnotationIntrospector();
+ final AnnotationIntrospector jacksonIntrospector = new JacksonAnnotationIntrospector();
+
+ return new AnnotationIntrospector.Pair(jacksonIntrospector, jaxbIntrospector);
+ }
+}
diff --git a/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/NonJaxbBean.java b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/NonJaxbBean.java
new file mode 100644
index 0000000..3568f39
--- /dev/null
+++ b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/NonJaxbBean.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson1;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class NonJaxbBean {
+
+ private String name = "non-JAXB-bean";
+ private String description = "I am not a JAXB bean, just an unannotated POJO";
+ private int[] array = {1, 1, 2, 3, 5, 8, 13, 21};
+
+ public int[] getArray() {
+ return array;
+ }
+
+ public void setArray(final int[] array) {
+ this.array = array;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+}
diff --git a/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/NonJaxbBeanResource.java b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/NonJaxbBeanResource.java
new file mode 100644
index 0000000..09abd34
--- /dev/null
+++ b/examples/json-jackson1/src/main/java/org/glassfish/jersey/examples/jackson1/NonJaxbBeanResource.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson1;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.server.JSONP;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("/nonJaxbResource")
+public class NonJaxbBeanResource {
+
+ @GET
+ @JSONP
+ @Produces({"application/javascript", MediaType.APPLICATION_JSON})
+ public NonJaxbBean getSimpleBeanJSONP() {
+ return new NonJaxbBean();
+ }
+}
diff --git a/examples/json-jackson1/src/test/java/org/glassfish/jersey/examples/jackson1/Jackson1Test.java b/examples/json-jackson1/src/test/java/org/glassfish/jersey/examples/jackson1/Jackson1Test.java
new file mode 100644
index 0000000..4d78f26
--- /dev/null
+++ b/examples/json-jackson1/src/test/java/org/glassfish/jersey/examples/jackson1/Jackson1Test.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jackson1;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.jackson1.Jackson1Feature;
+import org.glassfish.jersey.message.internal.MediaTypes;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class Jackson1Test extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ enable(TestProperties.DUMP_ENTITY);
+
+ return App.createApp();
+ }
+
+ @Override
+ protected void configureClient(final ClientConfig config) {
+ config.register(Jackson1Feature.class).register(MyObjectMapperProvider.class);
+ }
+
+ @Test
+ public void testEmptyArrayPresent() {
+ final String responseMsg = target("emptyArrayResource").request(MediaType.APPLICATION_JSON).get(String.class);
+ assertTrue(responseMsg.replaceAll("[ \t]*", "").contains("[]"));
+ }
+
+ @Test
+ public void testJSONPPresent() {
+ final String responseMsg = target("nonJaxbResource").request("application/javascript").get(String.class);
+ assertTrue(responseMsg.startsWith("callback("));
+ }
+
+ @Test
+ public void testJSONDoesNotReflectJSONPWrapper() {
+ final String responseMsg = target("nonJaxbResource").request("application/json").get(String.class);
+ assertTrue(!responseMsg.contains("jsonSource"));
+ }
+
+ @Test
+ public void testCombinedAnnotationResource() {
+ final String responseMsg = target("combinedAnnotations").request("application/json").get(String.class);
+ assertTrue(responseMsg.contains("account") && responseMsg.contains("value"));
+ }
+
+ @Test
+ public void testEmptyArrayBean() {
+ assertNotNull(target("emptyArrayResource").request(MediaType.APPLICATION_JSON).get(EmptyArrayBean.class));
+ }
+
+ @Test
+ public void testCombinedAnnotationBean() {
+ assertNotNull(target("combinedAnnotations").request("application/json").get(CombinedAnnotationBean.class));
+ }
+
+ @Test
+ @Ignore
+ // TODO un-ignore once a JSON reader for "application/javascript" is supported
+ public void testJSONPBean() {
+ assertNotNull(target("nonJaxbResource").request("application/javascript").get(NonJaxbBean.class));
+ }
+
+ /**
+ * Test if a WADL document is available at the relative path
+ * "application.wadl".
+ * <p/>
+ */
+ @Test
+ public void testApplicationWadl() {
+ final WebTarget target = target();
+ final String serviceWadl = target.path("application.wadl").request(MediaTypes.WADL_TYPE).get(String.class);
+
+ assertTrue(serviceWadl.length() > 0);
+ }
+
+ /**
+ * Test, that in case of malformed JSON, the jackson exception mappers will be used and the response will be
+ * 400 - bad request instead of 500 - server error
+ */
+ @Test
+ public void testExceptionMapping() {
+ enable(TestProperties.LOG_TRAFFIC);
+ // create a request with invalid json string to cause an exception in Jackson
+ final Response response = target().path("parseExceptionTest").request("application/json")
+ .put(Entity.entity("Malformed json string.", MediaType.valueOf("application/json")));
+
+ assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ }
+}
diff --git a/examples/json-jettison/README.MD b/examples/json-jettison/README.MD
new file mode 100644
index 0000000..31e83d2
--- /dev/null
+++ b/examples/json-jettison/README.MD
@@ -0,0 +1,64 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Jettison JAX-RS JSON Provider Example
+=====================================
+
+This example demonstrates how to use JSON representation of JAXB based
+resources with Jettison JSON provider.
+
+A simple web application is presented with two resources: a flight list
+and a aircraft type list. A client can obtain the former list or update
+it using XML or JSON representation. The second list is read-only and
+shows how to provide JSON array using List<JAXBBean> objects.
+
+Contents
+--------
+
+The flight list web resource is implemented by
+`org.glassfish.jersey.examples.jettison.FlightList` class.
+
+The aircraft type list web resource is implemented by
+`org.glassfish.jersey.examples.jettison.AircraftTypeList` class.
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP method
+------------------ | ------------------ | -------------
+**_/flights_** | FlightList | GET, PUT
+**_/aircrafts_** | AircraftTypeList | GET
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the JSON from JAXB example using [Grizzly](http://grizzly.java.net/)
+
+A [WADL description](http://wadl.java.net/#spec) may be accessed at the URL:
+
+- <http://localhost:8080/jettison/application.wadl>
+
+Following steps are using [cURL](http://curl.haxx.se/) command line tool:
+
+Get the list of flights in JSON format:
+
+> curl -HAccept:application/json http://localhost:8080/jettison/flights
+
+Get the list of flights in XML format:
+
+> curl -HAccept:application/xml http://localhost:8080/jettison/flights
+
+Get the list of aircraft types in JSON format:
+
+> curl -HAccept:application/json http://localhost:8080/jettison/aircrafts
+
+You can also see test classes at `src/main/test` subdirectory for detail
+information how to consume the service using JAX-RS client API
diff --git a/examples/json-jettison/pom.xml b/examples/json-jettison/pom.xml
new file mode 100644
index 0000000..2b1226a
--- /dev/null
+++ b/examples/json-jettison/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>json-jettison</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-json-jettison</name>
+
+ <description>Jersey JSON with Jettison JAXB example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jettison</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.jettison.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/AircraftType.java b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/AircraftType.java
new file mode 100644
index 0000000..36893fa
--- /dev/null
+++ b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/AircraftType.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jettison;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * TODO javadoc.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@XmlRootElement
+public class AircraftType {
+ public String type;
+ public double length;
+ public int seatingCapacity;
+
+ public AircraftType(){}
+
+ public AircraftType(String type, double length, int seatingCapacity) {
+ this.type = type;
+ this.length = length;
+ this.seatingCapacity = seatingCapacity;
+ }
+}
diff --git a/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/AircraftTypeList.java b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/AircraftTypeList.java
new file mode 100644
index 0000000..b091f52
--- /dev/null
+++ b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/AircraftTypeList.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jettison;
+
+import java.util.LinkedList;
+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;
+
+/**
+ * TODO javadoc.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("/aircrafts")
+public class AircraftTypeList {
+
+ static final List<AircraftType> aircraftTypes = new LinkedList<AircraftType>();
+
+ static {
+ aircraftTypes.add(new AircraftType("B737", 42.1, 204));
+ aircraftTypes.add(new AircraftType("A330", 58.8, 253));
+ }
+
+ @GET
+ @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+ public List<AircraftType> getAircraftTypes() {
+ return aircraftTypes;
+ }
+}
diff --git a/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/App.java b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/App.java
new file mode 100644
index 0000000..752e2e5
--- /dev/null
+++ b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/App.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jettison;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.jettison.JettisonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Utility class which can create {@link org.glassfish.jersey.server.ApplicationHandler} instance and provides support
+ * for running this sample from command line.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/jettison/");
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("JSON with JAXB Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, createApp(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.%nStop the application using CTRL+C"));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ public static ResourceConfig createApp() {
+ return new ResourceConfig()
+ .register(new JettisonFeature())
+ .packages("org.glassfish.jersey.examples.jettison");
+ }
+}
diff --git a/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/FlightList.java b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/FlightList.java
new file mode 100644
index 0000000..9c39717
--- /dev/null
+++ b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/FlightList.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jettison;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * TODO javadoc.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path(value = "/flights")
+public class FlightList {
+
+ @GET
+ @Produces({"application/json", "application/xml"})
+ public Flights getFlightList() {
+ return FlightsDataStore.getFlights();
+ }
+
+ @PUT
+ @Consumes({"application/json", "application/xml"})
+ public void putFlightList(Flights flights) {
+ FlightsDataStore.init(flights);
+ }
+
+ @POST
+ @Path("init")
+ @Produces({"application/json", "application/xml"})
+ public void initData() {
+ FlightsDataStore.init();
+ }
+
+}
diff --git a/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/FlightType.java b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/FlightType.java
new file mode 100644
index 0000000..e88161a
--- /dev/null
+++ b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/FlightType.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+//
+// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.0 in JDK 1.6
+// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
+// Any modifications to this file will be lost upon recompilation of the source schema.
+// Generated on: 2007.07.13 at 03:56:53 PM CEST
+//
+package org.glassfish.jersey.examples.jettison;
+
+import java.util.Formatter;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * <p>Java class for flightType complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType name="flightType">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="company" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * <element name="number" type="{http://www.w3.org/2001/XMLSchema}long"/>
+ * <element name="aircraft" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * </sequence>
+ * <attribute name="flightId" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "flightType", propOrder = {
+ "flightId",
+ "company",
+ "number",
+ "aircraft"
+})
+public class FlightType {
+
+ @XmlElement(required = true)
+ protected String company;
+ protected long number;
+ @XmlElement(required = true)
+ protected String aircraft;
+ @XmlElement(required = true)
+ protected String flightId;
+
+ /**
+ * Gets the value of the company property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getCompany() {
+ return company;
+ }
+
+ /**
+ * Sets the value of the company property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setCompany(String value) {
+ this.company = value;
+ }
+
+ /**
+ * Gets the value of the number property.
+ *
+ * @return flight number.
+ */
+ public long getNumber() {
+ return number;
+ }
+
+ /**
+ * Sets the value of the number property.
+ *
+ * @param value new flight number.
+ */
+ public void setNumber(long value) {
+ this.number = value;
+ }
+
+ /**
+ * Gets the value of the aircraft property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getAircraft() {
+ return aircraft;
+ }
+
+ /**
+ * Sets the value of the aircraft property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setAircraft(String value) {
+ this.aircraft = value;
+ }
+
+ /**
+ * Gets the value of the flightId property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getFlightId() {
+ return flightId;
+ }
+
+ /**
+ * Sets the value of the flightId property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setFlightId(String value) {
+ this.flightId = value;
+ }
+
+ @Override
+ public String toString() {
+ return (new Formatter()).format(
+ "{\"flightId\":\"%s\",\"company\":\"%s\",\"number\":%d,\"aircraft\":\"%s\"}",
+ this.flightId, this.company, this.number, this.aircraft).toString();
+ }
+}
diff --git a/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/Flights.java b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/Flights.java
new file mode 100644
index 0000000..325fbce
--- /dev/null
+++ b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/Flights.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+//
+// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.0 in JDK 1.6
+// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
+// Any modifications to this file will be lost upon recompilation of the source schema.
+// Generated on: 2007.07.13 at 03:56:53 PM CEST
+//
+package org.glassfish.jersey.examples.jettison;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * <p>Java class for anonymous complex type.
+ *
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ *
+ * <pre>
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="flight" type="{}flightType" maxOccurs="unbounded"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </pre>
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {
+ "flight"
+})
+@XmlRootElement(name = "flights")
+public class Flights {
+
+ @XmlElement(required = true)
+ protected List<FlightType> flight;
+
+ /**
+ * Gets the value of the flight property.
+ *
+ * <p>
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a <CODE>set</CODE> method for the flight property.
+ *
+ * <p>
+ * For example, to add a new item, do as follows:
+ * <pre>
+ * getFlight().add(newItem);
+ * </pre>
+ *
+ *
+ * <p>
+ * Objects of the following type(s) are allowed in the list
+ * {@link FlightType }
+ *
+ *
+ * @return list of flights.
+ */
+ public List<FlightType> getFlight() {
+ if (flight == null) {
+ flight = new ArrayList<FlightType>();
+ }
+ return this.flight;
+ }
+
+ @Override
+ public String toString() {
+ return (this.flight != null) ? "{\"flight\": " + this.flight.toString() + "}" : "{flight:[]}";
+ }
+}
diff --git a/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/FlightsDataStore.java b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/FlightsDataStore.java
new file mode 100644
index 0000000..4cbe12d
--- /dev/null
+++ b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/FlightsDataStore.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jettison;
+
+/**
+ * TODO javadoc.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class FlightsDataStore {
+
+ private static volatile Flights flightsData = initFlightsData();
+
+ public static void init() {
+ init(initFlightsData());
+ }
+
+ public static void init(final Flights flights) {
+ flightsData = flights;
+ }
+
+ public static Flights getFlights() {
+ return flightsData;
+ }
+
+ private static Flights initFlightsData() {
+ Flights flights = new Flights();
+ FlightType flight123 = new FlightType();
+ flight123.setCompany("Czech Airlines");
+ flight123.setNumber(123);
+ flight123.setFlightId("OK123");
+ flight123.setAircraft("B737");
+ FlightType flight124 = new FlightType();
+ flight124.setCompany("Czech Airlines");
+ flight124.setNumber(124);
+ flight124.setFlightId("OK124");
+ flight124.setAircraft("AB115");
+ flights.getFlight().add(flight123);
+ flights.getFlight().add(flight124);
+
+ return flights;
+ }
+}
diff --git a/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/JaxbContextResolver.java b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/JaxbContextResolver.java
new file mode 100644
index 0000000..342d61e
--- /dev/null
+++ b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/JaxbContextResolver.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jettison;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.ext.ContextResolver;
+import javax.ws.rs.ext.Provider;
+
+import javax.xml.bind.JAXBContext;
+
+import org.glassfish.jersey.jettison.JettisonConfig;
+import org.glassfish.jersey.jettison.JettisonJaxbContext;
+
+/**
+ * {@link ContextResolver Context resolver} for {@link JAXBContext}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Provider
+public final class JaxbContextResolver implements ContextResolver<JAXBContext> {
+
+ private final JAXBContext context;
+ private final Set<Class<?>> types;
+ private final Class<?>[] cTypes = {Flights.class, FlightType.class, AircraftType.class};
+
+ public JaxbContextResolver() throws Exception {
+ this.types = new HashSet<Class<?>>(Arrays.asList(cTypes));
+ this.context = new JettisonJaxbContext(JettisonConfig.DEFAULT, cTypes);
+ }
+
+ @Override
+ public JAXBContext getContext(Class<?> objectType) {
+ return (types.contains(objectType)) ? context : null;
+ }
+}
diff --git a/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/ObjectFactory.java b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/ObjectFactory.java
new file mode 100644
index 0000000..b16560b
--- /dev/null
+++ b/examples/json-jettison/src/main/java/org/glassfish/jersey/examples/jettison/ObjectFactory.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+//
+// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.0 in JDK 1.6
+// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
+// Any modifications to this file will be lost upon recompilation of the source schema.
+// Generated on: 2007.07.13 at 03:56:53 PM CEST
+//
+package org.glassfish.jersey.examples.jettison;
+
+import javax.xml.bind.annotation.XmlRegistry;
+
+
+/**
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.sun.jersey.samples.jsonfromjaxb.jaxb package.
+ * <p>An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+@XmlRegistry
+public class ObjectFactory {
+
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.sun.jersey.samples.jsonfromjaxb.jaxb
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link FlightType }
+ *
+ * @return flight type.
+ */
+ public FlightType createFlightType() {
+ return new FlightType();
+ }
+
+ /**
+ * Create an instance of {@link Flights }
+ *
+ * @return flights instance.
+ */
+ public Flights createFlights() {
+ return new Flights();
+ }
+
+}
diff --git a/examples/json-jettison/src/test/java/org/glassfish/jersey/examples/jettison/JsonJettisonTest.java b/examples/json-jettison/src/test/java/org/glassfish/jersey/examples/jettison/JsonJettisonTest.java
new file mode 100644
index 0000000..a72e521
--- /dev/null
+++ b/examples/json-jettison/src/test/java/org/glassfish/jersey/examples/jettison/JsonJettisonTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jettison;
+
+import java.util.List;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.jettison.JettisonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class JsonJettisonTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+
+ return App.createApp();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(new JettisonFeature()).register(JaxbContextResolver.class);
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // reset static flights list
+ target().path("flights/init").request("application/json").post(null);
+ }
+
+ /**
+ * Test checks that the application.wadl is reachable.
+ * <p/>
+ */
+ @Test
+ public void testApplicationWadl() {
+ String applicationWadl = target().path("application.wadl").request().get(String.class);
+ assertTrue("Something wrong. Returned wadl length is not > 0", applicationWadl.length() > 0);
+ }
+
+ /**
+ * Test check GET on the "flights" resource in "application/json" format.
+ */
+ @Test
+ public void testGetOnFlightsJSONFormat() {
+ // get the initial representation
+ Flights flights = target().path("flights").request("application/json").get(Flights.class);
+ // check that there are two flight entries
+ assertEquals("Expected number of initial entries not found", 2, flights.getFlight().size());
+ }
+
+ /**
+ * Test checks PUT on the "flights" resource in "application/json" format.
+ */
+ @Test
+ public void testPutOnFlightsJSONFormat() {
+ // get the initial representation
+ Flights flights = target().path("flights")
+ .request("application/json").get(Flights.class);
+ // check that there are two flight entries
+ assertEquals("Expected number of initial entries not found", 2, flights.getFlight().size());
+
+ // remove the second flight entry
+ if (flights.getFlight().size() > 1) {
+ flights.getFlight().remove(1);
+ }
+
+ // update the first entry
+ flights.getFlight().get(0).setNumber(125);
+ flights.getFlight().get(0).setFlightId("OK125");
+
+ // and send the updated list back to the server
+ target().path("flights").request().put(Entity.json(flights));
+
+ // get the updated list out from the server:
+ Flights updatedFlights = target().path("flights").request("application/json").get(Flights.class);
+ //check that there is only one flight entry
+ assertEquals("Remaining number of flight entries do not match the expected value", 1, updatedFlights.getFlight().size());
+ // check that the flight entry in retrieved list has FlightID OK!@%
+ assertEquals("Retrieved flight ID doesn't match the expected value", "OK125",
+ updatedFlights.getFlight().get(0).getFlightId());
+ }
+
+ /**
+ * Test checks GET on "flights" resource with mime-type "application/xml".
+ */
+ @Test
+ public void testGetOnFlightsXMLFormat() {
+ // get the initial representation
+ Flights flights = target().path("flights").request("application/xml").get(Flights.class);
+ // check that there are two flight entries
+ assertEquals("Expected number of initial entries not found", 2, flights.getFlight().size());
+ }
+
+ /**
+ * Test checks PUT on "flights" resource with mime-type "application/xml".
+ */
+ @Test
+ public void testPutOnFlightsXMLFormat() {
+ // get the initial representation
+ Flights flights = target().path("flights").request("application/XML").get(Flights.class);
+ // check that there are two flight entries
+ assertEquals("Expected number of initial entries not found", 2, flights.getFlight().size());
+
+ // remove the second flight entry
+ if (flights.getFlight().size() > 1) {
+ flights.getFlight().remove(1);
+ }
+
+ // update the first entry
+ flights.getFlight().get(0).setNumber(125);
+ flights.getFlight().get(0).setFlightId("OK125");
+
+ // and send the updated list back to the server
+ target().path("flights").request().put(Entity.xml(flights));
+
+ // get the updated list out from the server:
+ Flights updatedFlights = target().path("flights").request("application/XML").get(Flights.class);
+ //check that there is only one flight entry
+ assertEquals("Remaining number of flight entries do not match the expected value", 1, updatedFlights.getFlight().size());
+ // check that the flight entry in retrieved list has FlightID OK!@%
+ assertEquals("Retrieved flight ID doesn't match the expected value", "OK125",
+ updatedFlights.getFlight().get(0).getFlightId());
+ }
+
+ /**
+ * Test check GET on the "aircrafts" resource in "application/json" format.
+ */
+ @Test
+ public void testGetOnAircraftsJSONFormat() {
+ GenericType<List<AircraftType>> listOfAircrafts = new GenericType<List<AircraftType>>() {
+ };
+ // get the initial representation
+ List<AircraftType> aircraftTypes = target().path("aircrafts").request("application/json").get(listOfAircrafts);
+ // check that there are two aircraft type entries
+ assertEquals("Expected number of initial aircraft types not found", 2, aircraftTypes.size());
+ }
+}
diff --git a/examples/json-moxy/README.MD b/examples/json-moxy/README.MD
new file mode 100644
index 0000000..bd63590
--- /dev/null
+++ b/examples/json-moxy/README.MD
@@ -0,0 +1,41 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+MOXy JAX-RS JSON Provider Example
+=================================
+
+This example demonstrates how to produce/consume JSON representations from Java objects.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP method
+-------------- | ---------------- | -------------
+**_/test_** | JsonResource | GET
+**_/test_** | JsonResource | POST
+
+(See JsonResourceTest for more details.)
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/)
+
+A [WADL description](http://wadl.java.net/#spec) may be accessed at the URL:
+
+- <http://localhost:9998/jsonmoxy/application.wadl>
+
+The resource is available at
+
+- <http://localhost:9998/jsonmoxy/test>
\ No newline at end of file
diff --git a/examples/json-moxy/pom.xml b/examples/json-moxy/pom.xml
new file mode 100644
index 0000000..815ae38
--- /dev/null
+++ b/examples/json-moxy/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>json-moxy</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-json-moxy</name>
+
+ <description>Jersey JSON with MOXy example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-moxy</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.jsonmoxy.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/json-moxy/src/main/java/org/glassfish/jersey/examples/jsonmoxy/App.java b/examples/json-moxy/src/main/java/org/glassfish/jersey/examples/jsonmoxy/App.java
new file mode 100644
index 0000000..3715d11
--- /dev/null
+++ b/examples/json-moxy/src/main/java/org/glassfish/jersey/examples/jsonmoxy/App.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonmoxy;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.ext.ContextResolver;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.moxy.json.MoxyJsonConfig;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Michal Gajdos
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:9998/jsonmoxy/");
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("JSON with MOXy Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, createApp(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.%nStop the application using CTRL+C"));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ public static ResourceConfig createApp() {
+ return new ResourceConfig()
+ .packages("org.glassfish.jersey.examples.jsonmoxy")
+ .register(createMoxyJsonResolver());
+ }
+
+ public static ContextResolver<MoxyJsonConfig> createMoxyJsonResolver() {
+ final MoxyJsonConfig moxyJsonConfig = new MoxyJsonConfig();
+ Map<String, String> namespacePrefixMapper = new HashMap<String, String>(1);
+ namespacePrefixMapper.put("http://www.w3.org/2001/XMLSchema-instance", "xsi");
+ moxyJsonConfig.setNamespacePrefixMapper(namespacePrefixMapper).setNamespaceSeparator(':');
+ return moxyJsonConfig.resolver();
+ }
+}
+
+
+
diff --git a/examples/json-moxy/src/main/java/org/glassfish/jersey/examples/jsonmoxy/JsonResource.java b/examples/json-moxy/src/main/java/org/glassfish/jersey/examples/jsonmoxy/JsonResource.java
new file mode 100644
index 0000000..a26e430
--- /dev/null
+++ b/examples/json-moxy/src/main/java/org/glassfish/jersey/examples/jsonmoxy/JsonResource.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonmoxy;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Path("test")
+public class JsonResource {
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public TestBean createSimpleBean() {
+ return new TestBean("a", 1, 1L);
+ }
+
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public TestBean roundTrip(TestBean s) {
+ return s;
+ }
+}
diff --git a/examples/json-moxy/src/main/java/org/glassfish/jersey/examples/jsonmoxy/TestBean.java b/examples/json-moxy/src/main/java/org/glassfish/jersey/examples/jsonmoxy/TestBean.java
new file mode 100644
index 0000000..74f1e38
--- /dev/null
+++ b/examples/json-moxy/src/main/java/org/glassfish/jersey/examples/jsonmoxy/TestBean.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonmoxy;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@XmlRootElement
+public class TestBean {
+
+ public String a;
+ public int b;
+ public long c;
+
+ public TestBean() {
+ }
+
+ public TestBean(String a, int b, long c) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+
+ public String getA() {
+ return a;
+ }
+
+ public int getB() {
+ return b;
+ }
+
+ public long getC() {
+ return c;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ TestBean that = (TestBean) o;
+
+ if (b != that.b) {
+ return false;
+ }
+ if (c != that.c) {
+ return false;
+ }
+ if (a != null ? !a.equals(that.a) : that.a != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = a != null ? a.hashCode() : 0;
+ result = 31 * result + b;
+ result = 31 * result + (int) (c ^ (c >>> 32));
+ return result;
+ }
+}
diff --git a/examples/json-moxy/src/test/java/org/glassfish/jersey/examples/jsonmoxy/JsonResourceTest.java b/examples/json-moxy/src/test/java/org/glassfish/jersey/examples/jsonmoxy/JsonResourceTest.java
new file mode 100644
index 0000000..3f6000a
--- /dev/null
+++ b/examples/json-moxy/src/test/java/org/glassfish/jersey/examples/jsonmoxy/JsonResourceTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonmoxy;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Michal Gajdos
+ */
+public class JsonResourceTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ enable(TestProperties.DUMP_ENTITY);
+
+ return App.createApp();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(App.createMoxyJsonResolver());
+ }
+
+ @Test
+ public void testGet() {
+ final WebTarget target = target("test");
+ final TestBean testBean = target.request(MediaType.APPLICATION_JSON_TYPE).get(TestBean.class);
+
+ assertEquals(testBean, new TestBean("a", 1, 1L));
+ }
+
+ @Test
+ public void roundTripTest() {
+ final WebTarget target = target("test");
+ final TestBean testBean = target
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.entity(new TestBean("a", 1, 1L), MediaType.APPLICATION_JSON_TYPE), TestBean.class);
+
+ assertEquals(testBean, new TestBean("a", 1, 1L));
+ }
+}
diff --git a/examples/json-processing-webapp/README.MD b/examples/json-processing-webapp/README.MD
new file mode 100644
index 0000000..1d1750f
--- /dev/null
+++ b/examples/json-processing-webapp/README.MD
@@ -0,0 +1,61 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+JSON-P JAX-RS JSON Provider Example
+===================================
+
+This example demonstrates how to produce/consume JSON representations
+defined by [JSR-353](http://jcp.org/en/jsr/detail?id=353).
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Allowed values
+--------------------------- | --------------------------- | -------------- | ------------------------------
+**_/document_** | DocumentResource | GET |
+**_/document_** | DocumentResource | DELETE |
+**_/document_** | DocumentResource | POST | `JsonObject`
+**_/document/{id: \d+}_** | DocumentResource | GET |
+**_/document/{id: \d+}_** | DocumentResource | DELETE |
+**_/document/multiple_** | DocumentResource | POST | `JsonArray` of `JsonObject`s
+**_/document/filter_** | DocumentFilteringResource | POST | `JsonArray` of string values
+
+(See JsonProcessingResourceTest for more details.)
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean package jetty:run
+
+A [WADL description](http://wadl.java.net/#spec) may be accessed at the URL:
+
+- <http://localhost:8080/jsonp-webapp/application.wadl>
+
+This deploys current example using Jetty. You can access the application at <http://localhost:8080/jsonp-webapp/document>
+
+You can access resources of this application also using curl:
+
+### Store some documents.
+
+> curl -H "Content-Type: application/json" -X POST --data '[{"name":"Jersey","site":"http://jersey.java.net"},{"age":33,"phone":"158158158","name":"Foo"},{"name":"JSON-P","site":"http://jsonp.java.net"}]' http://localhost:8080/jsonp-webapp/document/multiple
+
+### Retrieve stored documents.
+
+> curl http://localhost:8080/jsonp-webapp/document
+
+### Retrieve a document with id 1.
+
+> curl http://localhost:8080/jsonp-webapp/document/1
+
+### Retrieve `site` attributes with values of documents containing this attribute.
+
+> curl -H "Content-Type: application/json" -X POST --data '["site"]' http://localhost:8080/jsonp-webapp/document/filter
diff --git a/examples/json-processing-webapp/pom.xml b/examples/json-processing-webapp/pom.xml
new file mode 100644
index 0000000..e84003b
--- /dev/null
+++ b/examples/json-processing-webapp/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>json-processing-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-json-processing-webapp</name>
+
+ <description>Jersey JSON-P (JSR 353) example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-processing</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>maven-jetty-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/json-processing-webapp/src/main/java/org/glassfish/jersey/examples/jsonp/MyApplication.java b/examples/json-processing-webapp/src/main/java/org/glassfish/jersey/examples/jsonp/MyApplication.java
new file mode 100644
index 0000000..965da71
--- /dev/null
+++ b/examples/json-processing-webapp/src/main/java/org/glassfish/jersey/examples/jsonp/MyApplication.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonp;
+
+import javax.json.stream.JsonGenerator;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * @author Michal Gajdos
+ */
+public class MyApplication extends ResourceConfig {
+
+ public MyApplication() {
+ packages("org.glassfish.jersey.examples.jsonp.resource");
+ register(LoggingFeature.class);
+ property(JsonGenerator.PRETTY_PRINTING, true);
+ }
+}
+
+
+
diff --git a/examples/json-processing-webapp/src/main/java/org/glassfish/jersey/examples/jsonp/resource/DocumentFilteringResource.java b/examples/json-processing-webapp/src/main/java/org/glassfish/jersey/examples/jsonp/resource/DocumentFilteringResource.java
new file mode 100644
index 0000000..5ca9299
--- /dev/null
+++ b/examples/json-processing-webapp/src/main/java/org/glassfish/jersey/examples/jsonp/resource/DocumentFilteringResource.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonp.resource;
+
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonString;
+
+import org.glassfish.jersey.examples.jsonp.service.DocumentStorage;
+
+/**
+ * Resource filtering stored documents based on the presence of given attributes.
+ *
+ * @author Michal Gajdos
+ */
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public class DocumentFilteringResource {
+
+ @POST
+ public JsonArray filter(final JsonArray properties) {
+ final JsonArrayBuilder documents = Json.createArrayBuilder();
+ final List<JsonString> propertyList = properties.getValuesAs(JsonString.class);
+
+ for (final JsonObject jsonObject : DocumentStorage.getAll()) {
+ final JsonObjectBuilder documentBuilder = Json.createObjectBuilder();
+
+ for (final JsonString property : propertyList) {
+ final String key = property.getString();
+
+ if (jsonObject.containsKey(key)) {
+ documentBuilder.add(key, jsonObject.get(key));
+ }
+ }
+
+ final JsonObject document = documentBuilder.build();
+ if (!document.isEmpty()) {
+ documents.add(document);
+ }
+ }
+
+ return documents.build();
+ }
+
+}
diff --git a/examples/json-processing-webapp/src/main/java/org/glassfish/jersey/examples/jsonp/resource/DocumentResource.java b/examples/json-processing-webapp/src/main/java/org/glassfish/jersey/examples/jsonp/resource/DocumentResource.java
new file mode 100644
index 0000000..a2a42b9
--- /dev/null
+++ b/examples/json-processing-webapp/src/main/java/org/glassfish/jersey/examples/jsonp/resource/DocumentResource.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonp.resource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+
+import org.glassfish.jersey.examples.jsonp.service.DocumentStorage;
+
+/**
+ * Document Resource.
+ *
+ * @author Michal Gajdos
+ */
+@Path("document")
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public class DocumentResource {
+
+ @GET
+ public JsonArray getAll() {
+ final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+ for (final JsonObject document : DocumentStorage.getAll()) {
+ arrayBuilder.add(document);
+ }
+ return arrayBuilder.build();
+ }
+
+ @GET
+ @Path("{id: \\d+}")
+ public JsonObject get(@PathParam("id") final int id) {
+ return DocumentStorage.get(id);
+ }
+
+ @DELETE
+ @Path("{id: \\d+}")
+ public JsonObject remove(@PathParam("id") final int id) {
+ return DocumentStorage.remove(id);
+ }
+
+ @DELETE
+ public void removeAll() {
+ DocumentStorage.removeAll();
+ }
+
+ @POST
+ public JsonArray store(final JsonObject document) {
+ return Json.createArrayBuilder().add(DocumentStorage.store(document)).build();
+ }
+
+ @POST
+ @Path("multiple")
+ public JsonArray store(final JsonArray documents) {
+ final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+ for (final JsonObject document : documents.getValuesAs(JsonObject.class)) {
+ arrayBuilder.add(DocumentStorage.store(document));
+ }
+ return arrayBuilder.build();
+ }
+
+ @Path("filter")
+ public Class<DocumentFilteringResource> getFilteringResource() {
+ return DocumentFilteringResource.class;
+ }
+}
diff --git a/examples/json-processing-webapp/src/main/java/org/glassfish/jersey/examples/jsonp/service/DocumentStorage.java b/examples/json-processing-webapp/src/main/java/org/glassfish/jersey/examples/jsonp/service/DocumentStorage.java
new file mode 100644
index 0000000..2c587d2
--- /dev/null
+++ b/examples/json-processing-webapp/src/main/java/org/glassfish/jersey/examples/jsonp/service/DocumentStorage.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonp.service;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.json.JsonObject;
+
+/**
+ * Storage of documents.
+ *
+ * @author Michal Gajdos
+ */
+public final class DocumentStorage {
+
+ private static final Map<Integer, JsonObject> storage = new LinkedHashMap<>();
+ private static final AtomicInteger counter = new AtomicInteger(0);
+
+ public static int store(final JsonObject document) {
+ final int id = counter.addAndGet(1);
+ storage.put(id, document);
+ return id;
+ }
+
+ public static JsonObject get(final int id) {
+ return storage.get(id);
+ }
+
+ public static Collection<JsonObject> getAll() {
+ return storage.values();
+ }
+
+ public static JsonObject remove(final int id) {
+ return storage.remove(id);
+ }
+
+ public static void removeAll() {
+ storage.clear();
+ }
+
+ /**
+ * Prevent initialization.
+ */
+ private DocumentStorage() {
+ }
+}
diff --git a/examples/json-processing-webapp/src/main/webapp/WEB-INF/web.xml b/examples/json-processing-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..fea17eb
--- /dev/null
+++ b/examples/json-processing-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+ <servlet>
+ <servlet-name>org.glassfish.jersey.examples.jsonp.MyApplication</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.glassfish.jersey.examples.jsonp.MyApplication</param-value>
+ </init-param>
+ <init-param>
+ <param-name>javax.json.stream.JsonGenerator.prettyPrinting</param-name>
+ <param-value>true</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>org.glassfish.jersey.examples.jsonp.MyApplication</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+</web-app>
diff --git a/examples/json-processing-webapp/src/test/java/org/glassfish/jersey/examples/jsonp/JsonProcessingResourceTest.java b/examples/json-processing-webapp/src/test/java/org/glassfish/jersey/examples/jsonp/JsonProcessingResourceTest.java
new file mode 100644
index 0000000..9b686c1
--- /dev/null
+++ b/examples/json-processing-webapp/src/test/java/org/glassfish/jersey/examples/jsonp/JsonProcessingResourceTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonp;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Michal Gajdos
+ */
+public class JsonProcessingResourceTest extends JerseyTest {
+
+ private static final List<JsonObject> documents = new ArrayList<>();
+
+ static {
+ documents.add(Json.createObjectBuilder()
+ .add("name", "Jersey")
+ .add("site", "http://jersey.github.io")
+ .build()
+ );
+ documents.add(Json.createObjectBuilder()
+ .add("age", 33)
+ .add("phone", "158158158")
+ .add("name", "Foo")
+ .build()
+ );
+ documents.add(Json.createObjectBuilder()
+ .add("name", "JSON-P")
+ .add("site", "https://javaee.github.io/jsonp/")
+ .build()
+ );
+ }
+
+ @Override
+ protected Application configure() {
+ return new MyApplication();
+ }
+
+ @Override
+ protected URI getBaseUri() {
+ return UriBuilder.fromUri(super.getBaseUri()).path("json-processing-webapp").build();
+ }
+
+ @Test
+ public void testStoreGetRemoveDocument() throws Exception {
+ final JsonObject document = documents.get(0);
+
+ // Store.
+ final Response response = target("document").request(MediaType.APPLICATION_JSON).post(Entity.json(document));
+ assertEquals(200, response.getStatus());
+
+ final List<JsonNumber> ids = response.readEntity(JsonArray.class).getValuesAs(JsonNumber.class);
+ assertEquals(1, ids.size());
+
+ // Get.
+ final String id = ids.get(0).toString();
+ final WebTarget documentTarget = target("document").path(id);
+ final JsonObject storedDocument = documentTarget.request(MediaType.APPLICATION_JSON)
+ .get(JsonObject.class);
+ assertEquals(document, storedDocument);
+
+ // Remove.
+ final JsonObject removedDocument = documentTarget.request(MediaType.APPLICATION_JSON).delete(JsonObject.class);
+ assertEquals(document, removedDocument);
+
+ // Get.
+ final Response errorResponse = documentTarget.request(MediaType.APPLICATION_JSON).get();
+ assertEquals(204, errorResponse.getStatus());
+ }
+
+ private JsonArray getDocumentJsonArray() {
+ final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+ for (final JsonObject document : documents) {
+ arrayBuilder.add(document);
+ }
+ return arrayBuilder.build();
+ }
+
+ @Test
+ public void testStoreDocuments() throws Exception {
+ final Response response = target("document/multiple")
+ .request(MediaType.APPLICATION_JSON).post(Entity.json(getDocumentJsonArray()));
+
+ assertEquals(200, response.getStatus());
+
+ final List<JsonNumber> ids = response.readEntity(JsonArray.class).getValuesAs(JsonNumber.class);
+ assertEquals(documents.size(), ids.size());
+
+ // Remove All.
+ target("document").request().delete();
+ }
+
+ @Test
+ public void testFilterDocuments() throws Exception {
+ // Store documents.
+ target("document/multiple").request(MediaType.APPLICATION_JSON_TYPE).post(Entity.json(getDocumentJsonArray()));
+
+ // Filter.
+ JsonArray filter = Json.createArrayBuilder().add("site").build();
+ JsonArray filtered = target("document/filter").request(MediaType.APPLICATION_JSON)
+ .post(Entity.json(filter), JsonArray.class);
+
+ checkFilteredDocuments(filtered, 2, "site");
+
+ filter = Json.createArrayBuilder().add("site").add("age").build();
+ filtered = target("document/filter").request(MediaType.APPLICATION_JSON)
+ .post(Entity.json(filter), JsonArray.class);
+
+ checkFilteredDocuments(filtered, 3, "site", "age");
+
+ // Remove All.
+ target("document").request().delete();
+ }
+
+ private void checkFilteredDocuments(final JsonArray filtered, final int size, final String... properties) {
+ assertEquals(size, filtered.size());
+
+ Set<String> strings = Arrays.stream(properties).collect(Collectors.toSet());
+ for (final JsonObject document : filtered.getValuesAs(JsonObject.class)) {
+ for (final String property : document.keySet()) {
+ assertTrue(strings.contains(property));
+ }
+ }
+ }
+}
diff --git a/examples/json-with-padding/README.MD b/examples/json-with-padding/README.MD
new file mode 100644
index 0000000..0712a30
--- /dev/null
+++ b/examples/json-with-padding/README.MD
@@ -0,0 +1,71 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+JSONP Example
+=============
+
+This example demonstrates how to use JSONP wrapper in order to serve not
+only JSON but also a JavaScript expression representing a function call
+with the original JSON data as a parameter. The technique is well
+described e.g. at <http://en.wikipedia.org/wiki/JSON#JSONP>.
+
+A simple web application is presented with just one resource: a change
+record list. A client can obtain the list using XML or JSON or
+JavaScript representation. The last change record is also available in
+all three formats as a subresource of the previously mentioned list
+resource.
+
+Contents
+--------
+
+The change record list web resource is implemented by
+`org.glassfish.jersey.examples.jsonp.ChangeListResource` class.
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+----------------------- | ---------------- | --------------
+**_/changes_** | ChangeList | GET
+**_/changes/latest_** | ChangeList | GET
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the JSONP example using [Grizzly](http://grizzly.java.net/)
+
+A [WADL description](http://wadl.java.net/#spec) may be then accessed at the URL:
+
+- <http://localhost:8080/jsonp/application.wadl>
+
+Following steps are using [cURL](http://curl.haxx.se/) command line tool:
+
+Get the list of changes in JSON format:
+
+> curl -HAccept:application/json http://localhost:8080/jsonp/changes
+
+Get the list of changes in XML format:
+
+> curl -HAccept:application/xml http://localhost:8080/jsonp/changes
+
+Get the list of changes as a JavaScript callback:
+
+> curl -HAccept:application/x-javascript http://localhost:8080/jsonp/changes
+
+You can also see test classes at `src/main/test` subdirectory for detail
+information how to consume the service using Jersey client API
+
+### JavaScript Client
+
+After starting the service and refreshing this page, a list of changes
+should be displayed below:
+
+`You need to run the service first!`
\ No newline at end of file
diff --git a/examples/json-with-padding/pom.xml b/examples/json-with-padding/pom.xml
new file mode 100644
index 0000000..dc814a3
--- /dev/null
+++ b/examples/json-with-padding/pom.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>json-with-padding</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-json-with-padding</name>
+
+ <description>Jersey JSON with Padding example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-moxy</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.sun.xml.bind</groupId>
+ <artifactId>jaxb-impl</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.jsonp.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/json-with-padding/src/main/assembly/src.xml b/examples/json-with-padding/src/main/assembly/src.xml
new file mode 100644
index 0000000..6e47a4b
--- /dev/null
+++ b/examples/json-with-padding/src/main/assembly/src.xml
@@ -0,0 +1,28 @@
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<assembly>
+ <id>project</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <fileSets>
+ <fileSet>
+ <directory>.</directory>
+ <outputDirectory></outputDirectory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ <excludes>
+ <exclude>**/target/**</exclude>
+ </excludes>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/examples/json-with-padding/src/main/java/org/glassfish/jersey/examples/jsonp/App.java b/examples/json-with-padding/src/main/java/org/glassfish/jersey/examples/jsonp/App.java
new file mode 100644
index 0000000..7dbcc51
--- /dev/null
+++ b/examples/json-with-padding/src/main/java/org/glassfish/jersey/examples/jsonp/App.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonp;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Utility class which can create {@link org.glassfish.jersey.server.ApplicationHandler} instance and provides support
+ * for running this sample from command line.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/jsonp/");
+ public static final String ROOT_PATH = "changes";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("JSONP Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, createApp(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(
+ String.format("Application started.%nTry out %s%s%nStop the application using CTRL+C", BASE_URI, ROOT_PATH));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ public static ResourceConfig createApp() {
+ return new ResourceConfig(ChangeListResource.class);
+ }
+}
diff --git a/examples/json-with-padding/src/main/java/org/glassfish/jersey/examples/jsonp/ChangeListResource.java b/examples/json-with-padding/src/main/java/org/glassfish/jersey/examples/jsonp/ChangeListResource.java
new file mode 100644
index 0000000..e0b24a6
--- /dev/null
+++ b/examples/json-with-padding/src/main/java/org/glassfish/jersey/examples/jsonp/ChangeListResource.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonp;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+
+import org.glassfish.jersey.server.JSONP;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path(App.ROOT_PATH)
+@Produces({"application/x-javascript", "application/json", "application/xml"})
+public class ChangeListResource {
+
+ static final List<ChangeRecordBean> changes = new LinkedList<ChangeRecordBean>();
+
+ static {
+ changes.add(new ChangeRecordBean(false, 2, "title \"User Guide\" updated"));
+ changes.add(new ChangeRecordBean(true, 1, "fixed metadata"));
+ changes.add(new ChangeRecordBean(false, 91, "added index"));
+ changes.add(new ChangeRecordBean(false, 650, "\"Troubleshoothing\" chapter"));
+ changes.add(new ChangeRecordBean(false, 1, "fixing typo"));
+ }
+
+ @GET
+ @JSONP(queryParam = JSONP.DEFAULT_QUERY)
+ public List<ChangeRecordBean> getChanges(@QueryParam(JSONP.DEFAULT_QUERY) String callback, @QueryParam("type") int type) {
+ return changes;
+ }
+
+ @GET
+ @Path("latest")
+ @JSONP
+ public ChangeRecordBean getLastChange(@QueryParam("callback") String callback, @QueryParam("type") int type) {
+ return changes.get(changes.size() - 1);
+ }
+}
diff --git a/examples/json-with-padding/src/main/java/org/glassfish/jersey/examples/jsonp/ChangeRecordBean.java b/examples/json-with-padding/src/main/java/org/glassfish/jersey/examples/jsonp/ChangeRecordBean.java
new file mode 100644
index 0000000..cef2479
--- /dev/null
+++ b/examples/json-with-padding/src/main/java/org/glassfish/jersey/examples/jsonp/ChangeRecordBean.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonp;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@XmlRootElement(name = "change")
+public class ChangeRecordBean {
+
+ @XmlAttribute
+ public boolean madeByAdmin;
+ public int linesChanged;
+ public String logMessage;
+
+ /**
+ * No-arg constructor for JAXB
+ */
+ public ChangeRecordBean() {}
+
+ public ChangeRecordBean(boolean madeByAdmin, int linesChanged, String logMessage) {
+ this.madeByAdmin = madeByAdmin;
+ this.linesChanged = linesChanged;
+ this.logMessage = logMessage;
+ }
+}
diff --git a/examples/json-with-padding/src/test/java/org/glassfish/jersey/examples/jsonp/JsonWithPaddingTest.java b/examples/json-with-padding/src/test/java/org/glassfish/jersey/examples/jsonp/JsonWithPaddingTest.java
new file mode 100644
index 0000000..d589dc1
--- /dev/null
+++ b/examples/json-with-padding/src/test/java/org/glassfish/jersey/examples/jsonp/JsonWithPaddingTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.jsonp;
+
+import java.util.List;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class JsonWithPaddingTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ enable(TestProperties.DUMP_ENTITY);
+
+ return App.createApp();
+ }
+
+ /**
+ * Test checks that the application.wadl is reachable.
+ * <p/>
+ */
+ @Test
+ public void testApplicationWadl() {
+ WebTarget target = target();
+ String applicationWadl = target.path("application.wadl").request().get(String.class);
+ assertTrue("Something wrong. Returned wadl length is not > 0", applicationWadl.length() > 0);
+ }
+
+ /**
+ * Test check GET on the "changes" resource in "application/json" format.
+ */
+ @Test
+ public void testGetOnChangesJSONFormat() {
+ WebTarget target = target();
+ GenericType<List<ChangeRecordBean>> genericType = new GenericType<List<ChangeRecordBean>>() {};
+ // get the initial representation
+ List<ChangeRecordBean> changes = target.path("changes").request("application/json").get(genericType);
+ // check that there are two changes entries
+ assertEquals("Expected number of initial changes not found", 5, changes.size());
+ }
+
+ /**
+ * Test check GET on the "changes" resource in "application/xml" format.
+ */
+ @Test
+ public void testGetOnLatestChangeXMLFormat() {
+ WebTarget target = target();
+ ChangeRecordBean lastChange = target.path("changes/latest").request("application/xml").get(ChangeRecordBean.class);
+ assertEquals(1, lastChange.linesChanged);
+ }
+
+ /**
+ * Test check GET on the "changes" resource in "application/javascript" format.
+ */
+ @Test
+ public void testGetOnLatestChangeJavascriptFormat() {
+ WebTarget target = target();
+ String js = target.path("changes").request("application/x-javascript").get(String.class);
+ assertTrue(js.startsWith("callback"));
+ }
+
+ @Test
+ public void testGetOnLatestChangeJavascriptFormatDifferentCallback() {
+ WebTarget target = target();
+ String js = target.path("changes").queryParam("__callback", "parse").request("application/x-javascript")
+ .get(String.class);
+ assertTrue(js.startsWith("parse"));
+ }
+}
diff --git a/examples/managed-beans-webapp/README.MD b/examples/managed-beans-webapp/README.MD
new file mode 100644
index 0000000..9de8b1a
--- /dev/null
+++ b/examples/managed-beans-webapp/README.MD
@@ -0,0 +1,42 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Managed Beans Example
+=====================
+
+This example demonstrates managed bean support in Jersey. JAX-RS root
+resource classes are annotated with @ManagedBean, which means EE-related
+resources may be injected into instances of those classes.
+
+Content
+-------
+
+The example application includes two root resource classes that are
+treated as Java EE managed beans. One root resource class is managed in
+the default JAX-RS life-cycle (one instance per request) and the other
+is managed in the singleton life-cycle (one instance per web
+application).
+
+Two Java EE artifacts are injected into the singleton root resource. The
+first is a resource constant defined in the web.xml. The second is an
+entity manager factory to allow integration with JPA layer.
+
+Running the Example
+-------------------
+
+This sample utilizes Java EE features in GlassFish application server.
+
+The easiest way to get the application running is to build it and deploy
+as follows:
+
+ mvn clean package
+ $AS_HOME/asadmin deploy target/managed-beans-webapp.war
+
+From a web browser, visit:
+
+- <http://localhost:8080/managed-beans-webapp>
diff --git a/examples/managed-beans-webapp/pom.xml b/examples/managed-beans-webapp/pom.xml
new file mode 100644
index 0000000..6703aa6
--- /dev/null
+++ b/examples/managed-beans-webapp/pom.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>managed-beans-webapp</artifactId>
+ <name>jersey-examples-managed-beans-webapp</name>
+ <packaging>war</packaging>
+
+ <description>Jersey Managed Beans Web Application Example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.enterprise</groupId>
+ <artifactId>cdi-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.persistence</groupId>
+ <artifactId>persistence-api</artifactId>
+ </dependency>
+ </dependencies>
+
+ <profiles>
+ <profile>
+ <id>run-external-tests</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <jersey.config.test.container.factory>${external.container.factory}</jersey.config.test.container.factory>
+ <jersey.config.test.container.port>${external.container.port}</jersey.config.test.container.port>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <properties>
+ <!-- External test container configuration is done via properties to allow overriding via command line. -->
+ <external.container.factory>org.glassfish.jersey.test.external.ExternalTestContainerFactory</external.container.factory>
+ <external.container.port>8080</external.container.port>
+ <maven.test.skip>false</maven.test.skip>
+ </properties>
+ </profile>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <properties>
+ <maven.test.skip>true</maven.test.skip>
+ </properties>
+
+</project>
diff --git a/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/ManagedBeanException.java b/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/ManagedBeanException.java
new file mode 100644
index 0000000..6b24520
--- /dev/null
+++ b/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/ManagedBeanException.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedbeans.resources;
+
+/**
+ * Exception to be thrown and mapped by the web application.
+ *
+ * @author Paul Sandoz
+ */
+public class ManagedBeanException extends RuntimeException {
+
+}
diff --git a/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/ManagedBeanExceptionMapper.java b/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/ManagedBeanExceptionMapper.java
new file mode 100644
index 0000000..0d1d452
--- /dev/null
+++ b/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/ManagedBeanExceptionMapper.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedbeans.resources;
+
+import javax.ws.rs.container.ResourceContext;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import javax.annotation.ManagedBean;
+import javax.annotation.PostConstruct;
+
+/**
+ * Custom exception mapper.
+ *
+ * @author Paul Sandoz
+ */
+@Provider
+@ManagedBean
+public class ManagedBeanExceptionMapper implements ExceptionMapper<ManagedBeanException> {
+
+ @Context
+ private UriInfo uiFieldInject;
+
+ @Context
+ private ResourceContext rc;
+
+ private UriInfo uiMethodInject;
+
+ private UriInfo ui;
+
+ @Context
+ public void set(UriInfo ui) {
+ this.uiMethodInject = ui;
+ }
+
+ @PostConstruct
+ public void postConstruct() {
+ ensureInjected();
+ this.ui = uiMethodInject;
+ }
+
+ @Override
+ public Response toResponse(ManagedBeanException exception) {
+ ensureInjected();
+ return Response.serverError().entity(String.format("ManagedBeanException from %s", ui.getPath())).build();
+ }
+
+ private void ensureInjected() throws IllegalStateException {
+ if (uiFieldInject == null || uiMethodInject == null || rc == null) {
+ throw new IllegalStateException();
+ }
+ }
+}
diff --git a/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/ManagedBeanPerRequestResource.java b/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/ManagedBeanPerRequestResource.java
new file mode 100644
index 0000000..1b7387d
--- /dev/null
+++ b/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/ManagedBeanPerRequestResource.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedbeans.resources;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+import javax.annotation.ManagedBean;
+
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptors;
+import javax.interceptor.InvocationContext;
+
+/**
+ * JAX-RS root resource treated as managed bean.
+ *
+ * @author Paul Sandoz
+ */
+@Path("/managedbean/per-request")
+@ManagedBean
+public class ManagedBeanPerRequestResource {
+
+ @Context UriInfo ui;
+
+ @QueryParam("x") String x;
+
+ public static class MyInterceptor {
+
+ @AroundInvoke
+ public Object around(InvocationContext ctx) throws Exception {
+ return String.format("INTERCEPTED: %s", ctx.proceed());
+ }
+ }
+
+ @GET
+ @Produces("text/plain")
+ @Interceptors(MyInterceptor.class)
+ public String getMessage() {
+ return String.format("echo from %s: %s", ui.getPath(), x);
+ }
+
+ @Path("exception")
+ public String getException() {
+ throw new ManagedBeanException();
+ }
+}
diff --git a/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/ManagedBeanSingletonResource.java b/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/ManagedBeanSingletonResource.java
new file mode 100644
index 0000000..3b79391
--- /dev/null
+++ b/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/ManagedBeanSingletonResource.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedbeans.resources;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import javax.annotation.ManagedBean;
+import javax.annotation.Resource;
+import javax.inject.Singleton;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.PersistenceUnit;
+
+/**
+ * JAX-RS root resource treated as Java EE managed bean in singleton scope.
+ *
+ * @author Paul Sandoz
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("/managedbean/singleton")
+@Singleton
+@ManagedBean
+public class ManagedBeanSingletonResource {
+
+ /**
+ * Initial value should get injected by Java EE container.
+ */
+ @Resource(name = "injectedResource")
+ int counter = 0;
+
+ @Context
+ UriInfo ui;
+
+ private EntityManager entityManager;
+
+ /**
+ * Set entity manager based on injected entity manager factory.
+ *
+ * @param em entity manager factory injected by Java EE container.
+ */
+ @PersistenceUnit(unitName = "ManagedBeansPU")
+ void setEntityManager(final EntityManagerFactory em) {
+ entityManager = em.createEntityManager();
+ }
+
+ /**
+ * Provide textual representation of the internal counter.
+ * @return internal counter {@code String} representation
+ */
+ @GET
+ @Produces("text/plain")
+ public String getMessage() {
+ return Integer.toString(counter++);
+ }
+
+ /**
+ * Reset internal counter.
+ * @param i new counter value to be set.
+ */
+ @PUT
+ @Produces("text/plain")
+ public void putMessage(final int i) {
+ counter = i;
+ }
+
+ /**
+ * Throw a runtime exception, so that it could be mapped.
+ *
+ * @return nothing, just throw the custom exception.
+ */
+ @Path("exception")
+ public String getException() {
+ throw new ManagedBeanException();
+ }
+
+ /**
+ * Get JPA widget value.
+ *
+ * @param id widget id.
+ * @return value of the widget or 404 if given widget id is not found.
+ */
+ @Path("widget/{id: \\d+}")
+ @GET
+ public String getWidget(@PathParam("id") final int id) {
+ try {
+ return entityManager.find(Widget.class, id).val;
+ } catch (final NullPointerException ignored) {
+ throw new WebApplicationException(Response.Status.NOT_FOUND);
+ }
+ }
+
+ /**
+ * Set new widget value.
+ * @param id widget id.
+ * @param val new value to be associated with above specified id.
+ */
+ @Path("widget/{id: \\d+}")
+ @PUT
+ public void putWidget(@PathParam("id") final int id, final String val) {
+ entityManager.merge(new Widget(id, val));
+ }
+
+ /**
+ * Remove widget with given id.
+ * @param id id of the widget to be removed.
+ */
+ @Path("widget/{id: \\d+}")
+ @DELETE
+ public void deleteWidget(@PathParam("id") final int id) {
+ entityManager.remove(entityManager.find(Widget.class, id));
+ }
+}
diff --git a/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/MyApplication.java b/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/MyApplication.java
new file mode 100644
index 0000000..4bb428f
--- /dev/null
+++ b/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/MyApplication.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedbeans.resources;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+/**
+ * JAX-RS application.
+ *
+ * @author Jonathan Benoit
+ */
+@ApplicationPath("/app/*")
+public class MyApplication extends Application {
+ @Override
+ public Set<Class<?>> getClasses() {
+ final Set<Class<?>> classes = new HashSet<Class<?>>();
+ classes.add(ManagedBeanException.class);
+ classes.add(ManagedBeanSingletonResource.class);
+ classes.add(ManagedBeanPerRequestResource.class);
+ classes.add(ManagedBeanExceptionMapper.class);
+ return classes;
+ }
+}
diff --git a/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/Widget.java b/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/Widget.java
new file mode 100644
index 0000000..fff3dcd
--- /dev/null
+++ b/examples/managed-beans-webapp/src/main/java/org/glassfish/jersey/examples/managedbeans/resources/Widget.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedbeans.resources;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+/**
+ * Simple JPA entity made accessible via {@link ManagedBeanSingletonResource}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Entity
+public class Widget implements Serializable {
+
+ @Id
+ int id;
+
+ String val;
+
+ /**
+ * No-arg constructor to make JPA happy.
+ */
+ public Widget() {
+ }
+
+ /**
+ * Create a new widget with given id and value.
+ * @param id widget id
+ * @param val widget value
+ */
+ public Widget(int id, String val) {
+ this.id = id;
+ this.val = val;
+ }
+}
diff --git a/examples/managed-beans-webapp/src/main/resources/META-INF/persistence.xml b/examples/managed-beans-webapp/src/main/resources/META-INF/persistence.xml
new file mode 100644
index 0000000..11ccdd5
--- /dev/null
+++ b/examples/managed-beans-webapp/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
+ <persistence-unit name="ManagedBeansPU" transaction-type="JTA">
+ <jta-data-source>jdbc/__default</jta-data-source>
+ <properties>
+ <!-- drop and create tables at deployment -->
+ <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
+ <!-- log sqls executed in server.log -->
+ <property name="eclipselink.logging.level" value="FINE"/>
+ <!-- Instruct EclipseLink to not log execeptions it throws. Lets the application decide about it-->
+ <property name="eclipselink.logging.exceptions" value="false"/>
+ </properties>
+ </persistence-unit>
+</persistence>
diff --git a/examples/managed-beans-webapp/src/main/webapp/WEB-INF/web.xml b/examples/managed-beans-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..1e3283b
--- /dev/null
+++ b/examples/managed-beans-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+ <env-entry>
+ <env-entry-name>injectedResource</env-entry-name>
+ <env-entry-type>java.lang.Integer</env-entry-type>
+ <env-entry-value>3</env-entry-value>
+ </env-entry>
+</web-app>
diff --git a/examples/managed-beans-webapp/src/main/webapp/index.jsp b/examples/managed-beans-webapp/src/main/webapp/index.jsp
new file mode 100644
index 0000000..69c02f6
--- /dev/null
+++ b/examples/managed-beans-webapp/src/main/webapp/index.jsp
@@ -0,0 +1,32 @@
+<%--
+
+ Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%--
+ Document : index
+ Created on : Oct 22, 2009, 9:16:47 AM
+ Author : paulsandoz
+--%>
+
+<%@page contentType="text/html" pageEncoding="UTF-8"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Managed Beans WebApp</title>
+ </head>
+ <body>
+ <p>Per-request resource <a href="app/managedbean/per-request">managedbean/per-request</a></p>
+ <p>Singleton resource <a href="app/managedbean/singleton">managedbean/singleton</a></p>
+ </body>
+</html>
diff --git a/examples/managed-beans-webapp/src/test/java/org/glassfish/jersey/examples/managedbeans/ManagedBeanWebAppTest.java b/examples/managed-beans-webapp/src/test/java/org/glassfish/jersey/examples/managedbeans/ManagedBeanWebAppTest.java
new file mode 100644
index 0000000..a7e86b6
--- /dev/null
+++ b/examples/managed-beans-webapp/src/test/java/org/glassfish/jersey/examples/managedbeans/ManagedBeanWebAppTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedbeans;
+
+import java.net.URI;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.examples.managedbeans.resources.MyApplication;
+import org.glassfish.jersey.message.internal.MediaTypes;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Main test for the Managed Beans web application.
+ * The application must be deployed and running on a standalone GlassFish container.
+ * To run the tests then, you just launch the following command:
+ * <pre>
+ * mvn -DskipTests=false test</pre>
+ *
+ * @author Naresh Srinivas Bhimisetty
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class ManagedBeanWebAppTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ return new MyApplication();
+ }
+
+ @Override
+ protected URI getBaseUri() {
+ return UriBuilder.fromUri(super.getBaseUri()).path("managed-beans-webapp").path("app").build();
+ }
+
+ /**
+ * Test that provided query parameter makes it back.
+ */
+ @Test
+ public void testPerRequestResource() {
+ WebTarget perRequest = target().path("managedbean/per-request");
+
+ String responseMsg = perRequest.queryParam("x", "X").request().get(String.class);
+ assertThat(responseMsg, containsString("X"));
+ assertThat(responseMsg, startsWith("INTERCEPTED"));
+
+ responseMsg = perRequest.queryParam("x", "hi there").request().get(String.class);
+ assertThat(responseMsg, containsString("hi there"));
+ assertThat(responseMsg, startsWith("INTERCEPTED"));
+ }
+
+ /**
+ * Test that singleton counter gets incremented with each call and can be reset.
+ */
+ @Test
+ public void testSingletonResource() {
+ WebTarget singleton = target().path("managedbean/singleton");
+
+ String responseMsg = singleton.request().get(String.class);
+ assertThat(responseMsg, containsString("3"));
+
+ responseMsg = singleton.request().get(String.class);
+ assertThat(responseMsg, containsString("4"));
+
+ singleton.request().put(Entity.text("1"));
+
+ responseMsg = singleton.request().get(String.class);
+ assertThat(responseMsg, containsString("1"));
+
+ responseMsg = singleton.request().get(String.class);
+ assertThat(responseMsg, containsString("2"));
+ }
+
+ /**
+ * Test the JPA backend.
+ */
+ @Test
+ public void testWidget() {
+ WebTarget target = target().path("managedbean/singleton/widget");
+
+ final WebTarget widget = target.path("1");
+
+ assertThat(widget.request().get().getStatus(), is(404));
+
+ widget.request().put(Entity.text("One"));
+ assertThat(widget.request().get(String.class), is("One"));
+
+ widget.request().put(Entity.text("Two"));
+ assertThat(widget.request().get(String.class), is("Two"));
+
+ assertThat(widget.request().delete().getStatus(), is(204));
+
+ assertThat(widget.request().get().getStatus(), is(404));
+ }
+
+ /**
+ * Test exceptions are properly mapped.
+ */
+ @Test
+ public void testExceptionMapper() {
+
+ WebTarget singletonTarget = target().path("managedbean/singleton/exception");
+ WebTarget perRequestTarget = target().path("managedbean/per-request/exception");
+
+ _testExceptionOutput(singletonTarget, "singleton");
+ _testExceptionOutput(perRequestTarget, "per-request");
+ }
+
+ /**
+ * Test a non empty WADL is generated.
+ */
+ @Test
+ public void testApplicationWadl() {
+ WebTarget wadl = target().path("application.wadl");
+ String wadlDoc = wadl.request(MediaTypes.WADL_TYPE).get(String.class);
+
+ assertThat(wadlDoc.length(), is(not(0)));
+ }
+
+
+ private void _testExceptionOutput(WebTarget exceptionTarget, String thatShouldBePresentInResponseBody) {
+
+ Response exceptionResponse = exceptionTarget.request().get();
+ assertThat(exceptionResponse.getStatus(), is(500));
+
+ final String responseBody = exceptionResponse.readEntity(String.class);
+
+ assertThat(responseBody, containsString("ManagedBeanException"));
+ assertThat(responseBody, containsString(thatShouldBePresentInResponseBody));
+ }
+}
diff --git a/examples/managed-client-simple-webapp/README.MD b/examples/managed-client-simple-webapp/README.MD
new file mode 100644
index 0000000..1792137
--- /dev/null
+++ b/examples/managed-client-simple-webapp/README.MD
@@ -0,0 +1,43 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Managed Client Simple Example
+=============================
+
+Description
+-----------
+
+This sample shows usage of injected client managed by jersey container
+into a resource methods. The annotation org.glassfish.jersey.server.Uri
+is used to inject client for specified path. The jax-rs application
+contains standard resources and also resources which use injected client
+to query these standard resources. In other words the injected client
+invokes requests to the other resources from the same jax-rs
+application. Also there is a example of client requesting resource from
+outside of the application.
+
+Front page shows the table with resources exposed in deployed
+application with description. Simply just click the links to go to these
+resources.
+
+Key Features
+------------
+
+- `@Uri`
+- `@Path`, `@PathParam`
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean package jetty:run
+
+This deploys current example on the local host. You can then access
+frontpage at <http://localhost:8080/managed-client-simple-webapp/index.html>.
+This page contians description of all resources available in the application.
diff --git a/examples/managed-client-simple-webapp/pom.xml b/examples/managed-client-simple-webapp/pom.xml
new file mode 100644
index 0000000..2644e0b
--- /dev/null
+++ b/examples/managed-client-simple-webapp/pom.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>managed-client-simple-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-managed-client-simple-webapp</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>maven-jetty-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
diff --git a/examples/managed-client-simple-webapp/src/main/java/org/glassfish/jersey/examples/managedclientsimple/resources/ClientResource.java b/examples/managed-client-simple-webapp/src/main/java/org/glassfish/jersey/examples/managedclientsimple/resources/ClientResource.java
new file mode 100644
index 0000000..daa752d
--- /dev/null
+++ b/examples/managed-client-simple-webapp/src/main/java/org/glassfish/jersey/examples/managedclientsimple/resources/ClientResource.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclientsimple.resources;
+
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.Uri;
+
+/**
+ * A resource which use managed client injected by {@link org.glassfish.jersey.server.Uri @Uri annotation} to query
+ * external resources and resource from {@link StandardResource}.
+ *
+ * @author Miroslav Fuksa
+ *
+ */
+@Path("client")
+public class ClientResource {
+
+ /**
+ * Make request to external web site using injected client. The response from the injected client is then
+ * returned as a response from this resource method.
+ *
+ * @param webTarget Injected web target.
+ * @return Response.
+ */
+ @GET
+ @Produces(MediaType.TEXT_HTML)
+ @Path("glassfish")
+ public Response glassfish(@Uri("http://glassfish.java.net/") WebTarget webTarget) {
+ final Response response = webTarget.request().get();
+ return Response.fromResponse(response).build();
+ }
+
+ /**
+ * Query {@link StandardResource} and return result based on the results from methods of the {@link StandardResource}.
+ *
+ * @param dogWebTarget Injected client.
+ * @param catWebTarget Injected client.
+ * @param elefantWebTarget Injected client.
+ * @return String entity.
+ */
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ @Path("animals")
+ public String animals(@Uri("resource/dog") WebTarget dogWebTarget,
+ @Uri("resource/cat") WebTarget catWebTarget,
+ @Uri("resource/elefant") WebTarget elefantWebTarget) {
+
+ final String dog = dogWebTarget.request().get(String.class);
+ final String cat = catWebTarget.request().get(String.class);
+ final String elefant = elefantWebTarget.request().get(String.class);
+ return "Queried animals: " + dog + " and " + cat + " and " + elefant;
+ }
+
+ /**
+ * Query {@link StandardResource} using a injected client. The client injection is using a template parameter {@code id}
+ * which is filled by JAX-RS implementation using a path parameter of this resource method.
+ *
+ * @param webTarget Injected client.
+ * @param id Path parameter.
+ * @return String entity.
+ */
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ @Path("car/{id}")
+ public String car(@Uri("resource/car/{id}") WebTarget webTarget, @PathParam("id") String id) {
+ final Response response = webTarget.request().get();
+ return "Response from resource/car/" + id + ": " + response.readEntity(String.class);
+ }
+}
diff --git a/examples/managed-client-simple-webapp/src/main/java/org/glassfish/jersey/examples/managedclientsimple/resources/ManagedClientApplication.java b/examples/managed-client-simple-webapp/src/main/java/org/glassfish/jersey/examples/managedclientsimple/resources/ManagedClientApplication.java
new file mode 100644
index 0000000..d008eed
--- /dev/null
+++ b/examples/managed-client-simple-webapp/src/main/java/org/glassfish/jersey/examples/managedclientsimple/resources/ManagedClientApplication.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclientsimple.resources;
+
+import javax.ws.rs.ApplicationPath;
+
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * {@link ResourceConfig resource config} registering resource for managed client simple sample.
+ *
+ * @author Miroslav Fuksa
+ */
+@ApplicationPath("/app")
+public class ManagedClientApplication extends ResourceConfig {
+
+ public ManagedClientApplication() {
+ super(ClientResource.class, StandardResource.class);
+ }
+
+}
diff --git a/examples/managed-client-simple-webapp/src/main/java/org/glassfish/jersey/examples/managedclientsimple/resources/StandardResource.java b/examples/managed-client-simple-webapp/src/main/java/org/glassfish/jersey/examples/managedclientsimple/resources/StandardResource.java
new file mode 100644
index 0000000..57c4ee7
--- /dev/null
+++ b/examples/managed-client-simple-webapp/src/main/java/org/glassfish/jersey/examples/managedclientsimple/resources/StandardResource.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclientsimple.resources;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+
+/**
+ * Standard example resource exposing GET methods. This resource can be accessed directly by GET and is also
+ * accessed from {@link ClientResource} using injected client.
+ *
+ * @author Miroslav Fuksa
+ *
+ */
+@Path("resource")
+public class StandardResource {
+ @GET
+ @Path("dog")
+ public String get() {
+ return "Max";
+ }
+
+ @GET
+ @Path("cat")
+ public String cat() {
+ return "Lucy";
+ }
+
+ @GET
+ @Path("elefant")
+ public String elefant() {
+ return "Bobo";
+ }
+
+ /**
+ * Returns resource based on the id which is passed as path parameter. For purpose of this sample the method
+ * just use the path parameter id to construct a string which is returned.
+ *
+ * @param id Injected path parameter.
+ * @return Resource constructed from given id.
+ */
+ @GET
+ @Path("car/{id}")
+ public String car(@PathParam("id") String id) {
+ return "CAR with id=" + id;
+ }
+}
diff --git a/examples/managed-client-simple-webapp/src/main/webapp/WEB-INF/web.xml b/examples/managed-client-simple-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..1c36ab2
--- /dev/null
+++ b/examples/managed-client-simple-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+ <servlet>
+ <servlet-name>managedclientsimple.resources.ManagedClientApplication</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.glassfish.jersey.examples.managedclientsimple.resources.ManagedClientApplication</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>managedclientsimple.resources.ManagedClientApplication</servlet-name>
+ <url-pattern>/app/*</url-pattern>
+ </servlet-mapping>
+
+ <welcome-file-list>
+ <welcome-file>
+ index.html
+ </welcome-file>
+ </welcome-file-list>
+</web-app>
diff --git a/examples/managed-client-simple-webapp/src/main/webapp/index.html b/examples/managed-client-simple-webapp/src/main/webapp/index.html
new file mode 100644
index 0000000..beb6acc
--- /dev/null
+++ b/examples/managed-client-simple-webapp/src/main/webapp/index.html
@@ -0,0 +1,97 @@
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+ <title>Managed client sample</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=MacRoman">
+
+ <style>
+ table, tr, th, td {
+ border: 1px solid;
+ border-collapse: collapse;
+ padding: 2px;
+ }
+ </style>
+
+</head>
+
+<body>
+ <h1>Managed client simple sample</h1>
+ <p>This sample shows using client injected by @Uri into the resource methods. The following table contains description
+ of all accessible resource.</p>
+ <table>
+ <tr>
+ <td>uri</td>
+ <td>http method</td>
+ <td>description</td>
+ </tr>
+ <tr>
+ <td>/index.html</td>
+ <td>GET</td>
+ <td>this page</td>
+ </tr>
+ <tr>
+ <td>/app/*</td>
+ <td></td>
+ <td>rest application is deployed under this path</td>
+ </tr>
+ <tr>
+ <td>/app/resource/*</td>
+ <td></td>
+ <td>standard sample resource with sub resource methods</td>
+ </tr>
+ <tr>
+ <td><a href="./app/resource/dog">./app/resource/dog</a></td>
+ <td>GET</td>
+ <td>resource (plain/text)</td>
+ </tr>
+ <tr>
+ <td><a href="./app/resource/cat">./app/resource/cat</a></td>
+ <td>GET</td>
+ <td>resource (plain/text)</td>
+ </tr>
+ <tr>
+ <td><a href="./app/resource/elefant">./app/resource/elefant</a></td>
+ <td>GET</td>
+ <td>resource (plain/text)</td>
+ </tr>
+ <tr>
+ <td><a href="./app/resource/car/80">./app/resource/car/{id}</a></td>
+ <td>GET</td>
+ <td>resource with path param (plain/text). {id} is a string path parameter. Example: ./app/resource/car/80</td>
+ </tr>
+
+ <tr>
+ <td>/app/client/*</td>
+ <td></td>
+ <td>Resource with sub resources that use injected clients to query sub resources of <b>/app/resource/*</b> and other resources</td>
+ </tr>
+ <tr>
+ <td><a href="./app/client/glassfish">./app/client/glassfish</a></td>
+ <td>GET</td>
+ <td>Queries by GET <a href="http://glassfish.java.net">http://glassfish.java.net</a> and returns target html page.</td>
+ </tr>
+ <tr>
+ <td><a href="./app/client/animals">./app/client/animals</a></td>
+ <td>GET</td>
+ <td>Queries <a href="./app/resource/dog">./app/resource/dog</a>, <a href="./app/resource/cat">./app/resource/cat</a> and <a href="./app/resource/elefant">./app/resource/elefant</a> and returns merged result</td>
+ </tr>
+ <tr>
+ <td><a href="./app/client/car/15">./app/client/car/{id}</a></td>
+ <td>GET</td>
+ <td>Queries /app/resource/car/{id} and returns result. {id} is a string path parameter. Example: ./app/resource/client/car/15</td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/examples/managed-client-simple-webapp/src/test/java/org/glassfish/jersey/examples/managedclientsimple/ManagedClientSimpleTest.java b/examples/managed-client-simple-webapp/src/test/java/org/glassfish/jersey/examples/managedclientsimple/ManagedClientSimpleTest.java
new file mode 100644
index 0000000..8610f89
--- /dev/null
+++ b/examples/managed-client-simple-webapp/src/test/java/org/glassfish/jersey/examples/managedclientsimple/ManagedClientSimpleTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclientsimple;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import java.net.URI;
+
+import org.glassfish.jersey.examples.managedclientsimple.resources.ManagedClientApplication;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.external.ExternalTestContainerFactory;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Miroslav Fuksa
+ *
+ */
+public class ManagedClientSimpleTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ return new ManagedClientApplication();
+ }
+
+ @Override
+ protected URI getBaseUri() {
+ final UriBuilder baseUriBuilder = UriBuilder.fromUri(super.getBaseUri()).path("managed-client-simple-webapp");
+ final boolean externalFactoryInUse = getTestContainerFactory() instanceof ExternalTestContainerFactory;
+ return externalFactoryInUse ? baseUriBuilder.path("app").build() : baseUriBuilder.build();
+ }
+
+ @Test
+ public void testManagedClientSimple() throws Exception {
+ final WebTarget resource = target().path("client");
+ Response response;
+
+ response = resource.path("animals").request(MediaType.TEXT_PLAIN).get();
+ assertEquals(200, response.getStatus());
+ assertEquals("Queried animals: Max and Lucy and Bobo", response.readEntity(String.class));
+
+ response = resource.path("car/1").request(MediaType.TEXT_PLAIN).get();
+ assertEquals(200, response.getStatus());
+ assertEquals("Response from resource/car/1: CAR with id=1", response.readEntity(String.class));
+ }
+
+}
+
diff --git a/examples/managed-client-webapp/README.MD b/examples/managed-client-webapp/README.MD
new file mode 100644
index 0000000..4937d1b
--- /dev/null
+++ b/examples/managed-client-webapp/README.MD
@@ -0,0 +1,81 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Managed Client Example
+======================
+
+This example demonstrates a simple usage of Jersey Managed Client
+feature.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+------------------- | ------------------ | --------------
+**_/public/a_** | PublicResource | GET
+**_/public/b_** | PublicResource | GET
+**_/internal/a_** | InternalResource | GET
+**_/internal/b_** | InternalResource | GET
+
+In the example, the requests to a *public resource* deployed on
+`/public/` path are forwarded to an *internal resource* (deployed on
+`/internal/` path) using injected
+[javax.ws.rs.client.WebTarget](https://jax-rs-spec.java.net/nonav/2.0/apidocs/javax/ws/rs/client/WebTarget.html)
+instances produced using 2 separate managed clients each of the managed
+clients using it's own custom configuration.
+
+An access to internal resource methods is guarded by a container request
+filter (`CustomHeaderFilter`) which rejects any request that does not
+contain expected custom header set to an expected value. In the example,
+the 2 managed clients (used by a public resource to access the methods
+on the internal resource) are configured with custom configurations,
+each containing a registration of a custom client request filter that is
+instructed to append a required custom header and value to every
+outgoing request. Only with managed client support working properly, the
+public resource is able to successfully retrieve data from the internal
+resource.
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean package jetty:run
+
+This deploys current example on the local host. You can then access WADL
+description of the deployed application at
+<http://localhost:8080/managed-client-webapp/application.wadl>.
+
+You can access public resource of this application using curl:
+
+> curl -v -H "Accept: text/plain" http://localhost:8080/managed-client-webapp/public/a
+
+> curl -v -H "Accept: text/plain" http://localhost:8080/managed-client-webapp/public/b
+
+In this example you should see the returned response message body
+contains "a" or "b" respectively upon successful invocation.
+
+You may also verify that access to internal resource is not possible
+without including a proper header in the request. First try to access
+the internal resource without any custom header:
+
+> curl -v -H "Accept: text/plain" http://localhost:8080/managed-client-webapp/internal/a
+
+> curl -v -H "Accept: text/plain" http://localhost:8080/managed-client-webapp/internal/b
+
+In both cases a `HTTP 403 Forbidden.` response is returned. Now lets try
+to access the resource once again, but this time we'll include also the
+expected custom headers:
+
+> curl -v -H "Accept: text/plain" -H "custom-header:a" http://localhost:8080/managed-client-webapp/internal/a
+
+> curl -v -H "Accept: text/plain" -H "custom-header:b" http://localhost:8080/managed-client-webapp/internal/b
+
+Finally, you should see the invocation succeeded and the returned response message body contains "a" or "b" respectively.
\ No newline at end of file
diff --git a/examples/managed-client-webapp/pom.xml b/examples/managed-client-webapp/pom.xml
new file mode 100644
index 0000000..9f9287f
--- /dev/null
+++ b/examples/managed-client-webapp/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>managed-client-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-managed-client-webapp</name>
+
+ <description>Jersey managed client web application example.</description>
+
+ <dependencies>
+ <!-- uncomment to use Grizzly client -->
+ <!--dependency>
+ <groupId>org.glassfish.jersey.connectors</groupId>
+ <artifactId>jersey-grizzly-connector</artifactId>
+ <scope>test</scope>
+ </dependency-->
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>maven-jetty-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/ClientA.java b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/ClientA.java
new file mode 100644
index 0000000..48fda2b
--- /dev/null
+++ b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/ClientA.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.server.ClientBinding;
+
+/**
+ * Managed client configuration for client A.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@ClientBinding(configClass = MyApplication.MyClientAConfig.class)
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+public @interface ClientA {
+}
diff --git a/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/ClientB.java b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/ClientB.java
new file mode 100644
index 0000000..f78229f
--- /dev/null
+++ b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/ClientB.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.server.ClientBinding;
+
+/**
+ * Managed client configuration for client B.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@ClientBinding(configClass = MyApplication.MyClientBConfig.class)
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+public @interface ClientB {
+}
diff --git a/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFeature.java b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFeature.java
new file mode 100644
index 0000000..1b73af6
--- /dev/null
+++ b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFeature.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.ws.rs.container.DynamicFeature;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.FeatureContext;
+
+/**
+ * Dynamic feature that appends a properly configured {@link CustomHeaderFilter} instance
+ * to every method that is annotated with {@link org.glassfish.jersey.examples.managedclient.CustomHeaderFeature.Require @Require} internal feature
+ * annotation.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class CustomHeaderFeature implements DynamicFeature {
+
+ /**
+ * A method annotation to be placed on those resource methods to which a validating
+ * {@link CustomHeaderFilter} instance should be added.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Documented
+ @Target(ElementType.METHOD)
+ public static @interface Require {
+ /**
+ * Expected custom header name to be validated by the {@link CustomHeaderFilter}.
+ */
+ public String headerName();
+
+ /**
+ * Expected custom header value to be validated by the {@link CustomHeaderFilter}.
+ */
+ public String headerValue();
+ }
+
+ @Override
+ public void configure(ResourceInfo resourceInfo, FeatureContext context) {
+ final Require va = resourceInfo.getResourceMethod().getAnnotation(Require.class);
+ if (va != null) {
+ context.register(new CustomHeaderFilter(va.headerName(), va.headerValue()));
+ }
+ }
+}
diff --git a/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFilter.java b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFilter.java
new file mode 100644
index 0000000..a10414b
--- /dev/null
+++ b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFilter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.io.IOException;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ * A filter for appending and validating custom headers.
+ * <p>
+ * On the client side, appends a new custom request header with a configured name and value to each outgoing request.
+ * </p>
+ * <p>
+ * On the server side, validates that each request has a custom header with a configured name and value.
+ * If the validation fails a HTTP 403 response is returned.
+ * </p>
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class CustomHeaderFilter implements ContainerRequestFilter, ClientRequestFilter {
+ private final String headerName;
+ private final String headerValue;
+
+ public CustomHeaderFilter(String headerName, String headerValue) {
+ if (headerName == null || headerValue == null) {
+ throw new IllegalArgumentException("Header name and value must not be null.");
+ }
+ this.headerName = headerName;
+ this.headerValue = headerValue;
+ }
+
+ @Override
+ public void filter(ContainerRequestContext ctx) throws IOException { // validate
+ if (!headerValue.equals(ctx.getHeaderString(headerName))) {
+ ctx.abortWith(Response.status(Response.Status.FORBIDDEN)
+ .type(MediaType.TEXT_PLAIN)
+ .entity(String.format("Expected header '%s' not present or value not equal to '%s'", headerName, headerValue))
+ .build());
+ }
+ }
+
+ @Override
+ public void filter(ClientRequestContext ctx) throws IOException { // append
+ ctx.getHeaders().putSingle(headerName, headerValue);
+ }
+}
diff --git a/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/InternalResource.java b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/InternalResource.java
new file mode 100644
index 0000000..f715129
--- /dev/null
+++ b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/InternalResource.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+/**
+ * Internal resource accessed from the managed client resource.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("internal")
+public class InternalResource {
+
+ @GET
+ @Path("a")
+ @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "a")
+ public String getA() {
+ return "a";
+ }
+
+ @GET
+ @Path("b")
+ @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "b")
+ public String getB() {
+ return "b";
+ }
+}
diff --git a/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/MyApplication.java b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/MyApplication.java
new file mode 100644
index 0000000..1c8ccc6
--- /dev/null
+++ b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/MyApplication.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * Jersey managed client example application.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class MyApplication extends ResourceConfig {
+
+ /**
+ * Create JAX-RS application for the example.
+ */
+ public MyApplication() {
+ super(PublicResource.class, InternalResource.class, CustomHeaderFeature.class);
+ }
+
+ public static class MyClientAConfig extends ClientConfig {
+ public MyClientAConfig() {
+ this.register(new CustomHeaderFilter("custom-header", "a"));
+ }
+ }
+
+ public static class MyClientBConfig extends ClientConfig {
+ public MyClientBConfig() {
+ this.register(new CustomHeaderFilter("custom-header", "b"));
+ }
+ }
+}
diff --git a/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/PublicResource.java b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/PublicResource.java
new file mode 100644
index 0000000..17454bc
--- /dev/null
+++ b/examples/managed-client-webapp/src/main/java/org/glassfish/jersey/examples/managedclient/PublicResource.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.Uri;
+
+/**
+ * A resource that uses managed clients to retrieve values of internal
+ * resources 'A' and 'B', which are protected by a {@link CustomHeaderFilter}
+ * and require a specific custom header in a request to be set to a specific value.
+ * <p>
+ * Properly configured managed clients have a {@code CustomHeaderFilter} instance
+ * configured to insert the {@link CustomHeaderFeature.Require required} custom header
+ * with a proper value into the outgoing client requests.
+ * </p>
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("public")
+public class PublicResource {
+
+ @Uri("a") @ClientA // resolves to <base>/internal/a
+ private WebTarget targetA;
+
+ @GET
+ @Produces("text/plain")
+ @Path("a")
+ public String getTargetA() {
+ return targetA.request(MediaType.TEXT_PLAIN).get(String.class);
+ }
+
+ @GET
+ @Produces("text/plain")
+ @Path("b")
+ public Response getTargetB(@Uri("internal/b") @ClientB WebTarget targetB) {
+ return targetB.request(MediaType.TEXT_PLAIN).get();
+ }
+}
diff --git a/examples/managed-client-webapp/src/main/webapp/WEB-INF/web.xml b/examples/managed-client-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..dcc06b8
--- /dev/null
+++ b/examples/managed-client-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+ <servlet>
+ <servlet-name>org.glassfish.jersey.examples.managedclient.MyApplication</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.glassfish.jersey.examples.managedclient.MyApplication</param-value>
+ </init-param>
+ <init-param>
+ <param-name>org.glassfish.jersey.examples.managedclient.ClientA.baseUri</param-name>
+ <param-value>http://localhost:8080/managed-client-webapp/internal</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>org.glassfish.jersey.examples.managedclient.MyApplication</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+</web-app>
diff --git a/examples/managed-client-webapp/src/test/java/org/glassfish/jersey/examples/managedclient/ManagedClientTest.java b/examples/managed-client-webapp/src/test/java/org/glassfish/jersey/examples/managedclient/ManagedClientTest.java
new file mode 100644
index 0000000..adc3acd
--- /dev/null
+++ b/examples/managed-client-webapp/src/test/java/org/glassfish/jersey/examples/managedclient/ManagedClientTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.net.URI;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Jersey managed client example tests.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class ManagedClientTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.jdkhttp.JdkHttpServerTestContainerFactory
+ enable(TestProperties.LOG_TRAFFIC);
+ // enable(TestProperties.DUMP_ENTITY);
+
+ final MyApplication app = new MyApplication();
+ // overriding ClientA base Uri property for test purposes
+ app.property(ClientA.class.getName() + ".baseUri", UriBuilder.fromUri(getBaseUri()).path("internal").build());
+ return app;
+ }
+
+ @Override
+ protected URI getBaseUri() {
+ return UriBuilder.fromUri(super.getBaseUri()).path("managed-client-webapp").build();
+ }
+
+// Uncomment to use Grizzly async client
+// @Override
+// protected void configureClient(ClientConfig clientConfig) {
+// clientConfig.connector(new GrizzlyConnector(clientConfig));
+// }
+
+ /**
+ * Test that a connection via managed clients works properly.
+ *
+ * @throws Exception in case of test failure.
+ */
+ @Test
+ public void testManagedClient() throws Exception {
+ final WebTarget resource = target().path("public").path("{name}");
+ Response response;
+
+ response = resource.resolveTemplate("name", "a").request(MediaType.TEXT_PLAIN).get();
+ assertEquals(200, response.getStatus());
+ assertEquals("a", response.readEntity(String.class));
+
+ response = resource.resolveTemplate("name", "b").request(MediaType.TEXT_PLAIN).get();
+ assertEquals(200, response.getStatus());
+ assertEquals("b", response.readEntity(String.class));
+ }
+}
diff --git a/examples/managed-client/README.MD b/examples/managed-client/README.MD
new file mode 100644
index 0000000..249eeb9
--- /dev/null
+++ b/examples/managed-client/README.MD
@@ -0,0 +1,81 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Managed Client Example
+======================
+
+This example demonstrates a simple usage of Jersey Managed Client
+feature.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+------------------- | ------------------ | --------------
+**_/public/a_** | PublicResource | GET
+**_/public/b_** | PublicResource | GET
+**_/internal/a_** | InternalResource | GET
+**_/internal/b_** | InternalResource | GET
+
+In the example, the requests to a *public resource* deployed on
+`/public/` path are forwarded to an *internal resource* (deployed on
+`/internal/` path) using injected
+[javax.ws.rs.client.WebTarget](https://jax-rs-spec.java.net/nonav/2.0/apidocs/javax/ws/rs/client/WebTarget.html)
+instances produced using 2 separate managed clients each of the managed
+clients using it's own custom configuration.
+
+An access to internal resource methods is guarded by a container request
+filter (`CustomHeaderFilter`) which rejects any request that does not
+contain expected custom header set to an expected value. In the example,
+the 2 managed clients (used by a public resource to access the methods
+on the internal resource) are configured with custom configurations,
+each containing a registration of a custom client request filter that is
+instructed to append a required custom header and value to every
+outgoing request. Only with managed client support working properly, the
+public resource is able to successfully retrieve data from the internal
+resource.
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean package exec:java
+
+This deploys current example on the local host. You can then access WADL
+description of the deployed application at
+<http://localhost:8080/managed-client-webapp/application.wadl>.
+
+You can access public resource of this application using curl:
+
+> curl -v -H "Accept: text/plain" http://localhost:8080/managed-client-webapp/public/a
+
+> curl -v -H "Accept: text/plain" http://localhost:8080/managed-client-webapp/public/b
+
+In this example you should see the returned response message body
+contains "a" or "b" respectively upon successful invocation.
+
+You may also verify that access to internal resource is not possible
+without including a proper header in the request. First try to access
+the internal resource without any custom header:
+
+> curl -v -H "Accept: text/plain" http://localhost:8080/managed-client-webapp/internal/a
+
+> curl -v -H "Accept: text/plain" http://localhost:8080/managed-client-webapp/internal/b
+
+In both cases a `HTTP 403 Forbidden.` response is returned. Now lets try
+to access the resource once again, but this time we'll include also the
+expected custom headers:
+
+> curl -v -H "Accept: text/plain" -H "custom-header:a" http://localhost:8080/managed-client-webapp/internal/a
+
+> curl -v -H "Accept: text/plain" -H "custom-header:b" http://localhost:8080/managed-client-webapp/internal/b
+
+Finally, you should see the invocation succeeded and the returned response message body contains "a" or "b" respectively.
diff --git a/examples/managed-client/pom.xml b/examples/managed-client/pom.xml
new file mode 100644
index 0000000..ad922d4
--- /dev/null
+++ b/examples/managed-client/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>managed-client</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-managed-client</name>
+
+ <description>Jersey managed client example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ <!-- uncomment to use Grizzly client -->
+ <!--dependency>
+ <groupId>org.glassfish.jersey.connectors</groupId>
+ <artifactId>jersey-grizzly-connector</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency-->
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.managedclient.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/App.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/App.java
new file mode 100644
index 0000000..dc23e6d
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/App.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Jersey programmatic managed client example application.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/managedclient/");
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Managed Client\" Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, create(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.\nTry out public endpoints:\n %s%s\n %s%s\n"
+ + "Stop the application using CTRL+C",
+ BASE_URI, "public/a",
+ BASE_URI, "public/b"));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ /**
+ * Create JAX-RS application for the example.
+ *
+ * @return create JAX-RS application for the example.
+ */
+ public static ResourceConfig create() {
+ return new ResourceConfig(PublicResource.class, InternalResource.class, CustomHeaderFeature.class)
+ .property(ClientA.class.getName() + ".baseUri", BASE_URI.toString() + "internal");
+ }
+
+ public static class MyClientAConfig extends ClientConfig {
+
+ public MyClientAConfig() {
+ this.register(new CustomHeaderFilter("custom-header", "a"));
+ }
+ }
+
+ public static class MyClientBConfig extends ClientConfig {
+
+ public MyClientBConfig() {
+ this.register(new CustomHeaderFilter("custom-header", "b"));
+ }
+ }
+}
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/ClientA.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/ClientA.java
new file mode 100644
index 0000000..c67dd1e
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/ClientA.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.server.ClientBinding;
+
+/**
+ * Managed client configuration for client A.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@ClientBinding(configClass = App.MyClientAConfig.class)
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+public @interface ClientA {
+}
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/ClientB.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/ClientB.java
new file mode 100644
index 0000000..3813df9
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/ClientB.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.server.ClientBinding;
+
+/**
+ * Managed client configuration for client B.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@ClientBinding(configClass = App.MyClientBConfig.class)
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+public @interface ClientB {
+}
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFeature.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFeature.java
new file mode 100644
index 0000000..7c249a1
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFeature.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.ws.rs.container.DynamicFeature;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.FeatureContext;
+
+/**
+ *Dynamic feature that appends a properly configured {@link CustomHeaderFilter} instance
+ * to every method that is annotated with {@link Require @Require} internal feature
+ * annotation.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class CustomHeaderFeature implements DynamicFeature {
+
+ /**
+ * A method annotation to be placed on those resource methods to which a validating
+ * {@link CustomHeaderFilter} instance should be added.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Documented
+ @Target(ElementType.METHOD)
+ public static @interface Require {
+ /**
+ * Expected custom header name to be validated by the {@link CustomHeaderFilter}.
+ */
+ public String headerName();
+
+ /**
+ * Expected custom header value to be validated by the {@link CustomHeaderFilter}.
+ */
+ public String headerValue();
+ }
+
+ @Override
+ public void configure(ResourceInfo resourceInfo, FeatureContext context) {
+ final Require va = resourceInfo.getResourceMethod().getAnnotation(Require.class);
+ if (va != null) {
+ context.register(new CustomHeaderFilter(va.headerName(), va.headerValue()));
+ }
+ }
+}
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFilter.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFilter.java
new file mode 100644
index 0000000..a10414b
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/CustomHeaderFilter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import java.io.IOException;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ * A filter for appending and validating custom headers.
+ * <p>
+ * On the client side, appends a new custom request header with a configured name and value to each outgoing request.
+ * </p>
+ * <p>
+ * On the server side, validates that each request has a custom header with a configured name and value.
+ * If the validation fails a HTTP 403 response is returned.
+ * </p>
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class CustomHeaderFilter implements ContainerRequestFilter, ClientRequestFilter {
+ private final String headerName;
+ private final String headerValue;
+
+ public CustomHeaderFilter(String headerName, String headerValue) {
+ if (headerName == null || headerValue == null) {
+ throw new IllegalArgumentException("Header name and value must not be null.");
+ }
+ this.headerName = headerName;
+ this.headerValue = headerValue;
+ }
+
+ @Override
+ public void filter(ContainerRequestContext ctx) throws IOException { // validate
+ if (!headerValue.equals(ctx.getHeaderString(headerName))) {
+ ctx.abortWith(Response.status(Response.Status.FORBIDDEN)
+ .type(MediaType.TEXT_PLAIN)
+ .entity(String.format("Expected header '%s' not present or value not equal to '%s'", headerName, headerValue))
+ .build());
+ }
+ }
+
+ @Override
+ public void filter(ClientRequestContext ctx) throws IOException { // append
+ ctx.getHeaders().putSingle(headerName, headerValue);
+ }
+}
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/InternalResource.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/InternalResource.java
new file mode 100644
index 0000000..f715129
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/InternalResource.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+/**
+ * Internal resource accessed from the managed client resource.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("internal")
+public class InternalResource {
+
+ @GET
+ @Path("a")
+ @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "a")
+ public String getA() {
+ return "a";
+ }
+
+ @GET
+ @Path("b")
+ @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "b")
+ public String getB() {
+ return "b";
+ }
+}
diff --git a/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/PublicResource.java b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/PublicResource.java
new file mode 100644
index 0000000..3340c16
--- /dev/null
+++ b/examples/managed-client/src/main/java/org/glassfish/jersey/examples/managedclient/PublicResource.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.Uri;
+
+/**
+ * A resource that uses managed clients to retrieve values of internal
+ * resources 'A' and 'B', which are protected by a {@link CustomHeaderFilter}
+ * and require a specific custom header in a request to be set to a specific value.
+ * <p>
+ * Properly configured managed clients have a {@code CustomHeaderFilter} instance
+ * configured to insert the {@link CustomHeaderFeature.Require required} custom header
+ * with a proper value into the outgoing client requests.
+ * </p>
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("public")
+public class PublicResource {
+ @Uri("a") @ClientA // resolves to <base>/internal/a
+ private WebTarget targetA;
+
+ @GET
+ @Produces("text/plain")
+ @Path("a")
+ public String getTargetA() {
+ return targetA.request(MediaType.TEXT_PLAIN).get(String.class);
+ }
+
+ @GET
+ @Produces("text/plain")
+ @Path("b")
+ public Response getTargetB(@Uri("internal/b") @ClientB WebTarget targetB) {
+ return targetB.request(MediaType.TEXT_PLAIN).get();
+ }
+}
diff --git a/examples/managed-client/src/test/java/org/glassfish/jersey/examples/managedclient/ManagedClientTest.java b/examples/managed-client/src/test/java/org/glassfish/jersey/examples/managedclient/ManagedClientTest.java
new file mode 100644
index 0000000..566aedd
--- /dev/null
+++ b/examples/managed-client/src/test/java/org/glassfish/jersey/examples/managedclient/ManagedClientTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.managedclient;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Jersey managed client example tests.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class ManagedClientTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.jdkhttp.JdkHttpServerTestContainerFactory
+ enable(TestProperties.LOG_TRAFFIC);
+ // enable(TestProperties.DUMP_ENTITY);
+
+ // overriding ClientA base Uri property for test purposes
+ return App.create().property(ClientA.class.getName() + ".baseUri", this.getBaseUri().toString() + "internal");
+ }
+
+// Uncomment to use Grizzly async client
+// @Override
+// protected void configureClient(ClientConfig clientConfig) {
+// clientConfig.connector(new GrizzlyConnector(clientConfig));
+// }
+
+ /**
+ * Test that a connection via managed clients works properly.
+ *
+ * @throws Exception in case of test failure.
+ */
+ @Test
+ public void testManagedClient() throws Exception {
+ final WebTarget resource = target().path("public").path("{name}");
+ Response response;
+
+ response = resource.resolveTemplate("name", "a").request(MediaType.TEXT_PLAIN).get();
+ assertEquals(200, response.getStatus());
+ assertEquals("a", response.readEntity(String.class));
+
+ response = resource.resolveTemplate("name", "b").request(MediaType.TEXT_PLAIN).get();
+ assertEquals(200, response.getStatus());
+ assertEquals("b", response.readEntity(String.class));
+ }
+}
diff --git a/examples/multipart-webapp/README.MD b/examples/multipart-webapp/README.MD
new file mode 100644
index 0000000..656c05b
--- /dev/null
+++ b/examples/multipart-webapp/README.MD
@@ -0,0 +1,35 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Multipart Web app Example
+=========================
+
+This example demonstrates how to develop RESTful web service with
+demonstrating JAX-RS Integration with MIME MultiPart Message Formats and
+an EE 6 compliant Web container.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Description | Sample request using curl
+---------------------------- | ---------------------------------------------- | -----------------------------------------------------------------------------------------------
+**_/form/part_** | POST message returning entire string | `curl -X POST -F "part=part1" http://localhost:8080/multipart-webapp/form/part`
+**_/form/part-file-name_** | POST message returning part filename string. | Be sure to execute this curl from project directory where pom.xml resides
+ | | `curl -X POST -F "part=@pom.xml" http://localhost:8080/multipart-webapp/form/part-file-name`
+**_/form/xml-jaxb-part_** | POST message returning xml jaxb part string. | No curl sample available, please check test sources.
+
+Running the Example
+-------------------
+
+You can run the example using Jetty as follows:
+
+> `mvn clean package jetty:run`
+
+Following steps are using [cURL](http://curl.haxx.se/) command line tool:
\ No newline at end of file
diff --git a/examples/multipart-webapp/pom.xml b/examples/multipart-webapp/pom.xml
new file mode 100644
index 0000000..64a2057
--- /dev/null
+++ b/examples/multipart-webapp/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>multipart-webapp</artifactId>
+ <name>jersey-examples-multipart-webapp</name>
+ <packaging>war</packaging>
+
+ <description>Jersey Multipart example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-multipart</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>maven-jetty-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/multipart-webapp/src/main/java/org/glassfish/jersey/examples/multipart/webapp/Bean.java b/examples/multipart-webapp/src/main/java/org/glassfish/jersey/examples/multipart/webapp/Bean.java
new file mode 100644
index 0000000..dc59cd5
--- /dev/null
+++ b/examples/multipart-webapp/src/main/java/org/glassfish/jersey/examples/multipart/webapp/Bean.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.multipart.webapp;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class Bean {
+
+ public String value;
+
+ public Bean() {
+ }
+
+ public Bean(String str) {
+ value = str;
+ }
+
+}
diff --git a/examples/multipart-webapp/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartFieldInjectedResource.java b/examples/multipart-webapp/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartFieldInjectedResource.java
new file mode 100644
index 0000000..cddba3f
--- /dev/null
+++ b/examples/multipart-webapp/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartFieldInjectedResource.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.multipart.webapp;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
+import org.glassfish.jersey.media.multipart.FormDataParam;
+
+/**
+ * Field-injected version of the {@link FormDataParam} injection testing resource.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("/form-field-injected")
+public class MultiPartFieldInjectedResource {
+ @FormDataParam("string") private String s;
+ @FormDataParam("string") private FormDataContentDisposition sd;
+ @FormDataParam("bean") private Bean b;
+ @FormDataParam("bean") private FormDataContentDisposition bd;
+
+ @POST
+ @Path("xml-jaxb-part")
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ public String post() {
+ return s + ":" + sd.getFileName() + "," + b.value + ":" + bd.getFileName();
+ }
+}
diff --git a/examples/multipart-webapp/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartResource.java b/examples/multipart-webapp/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartResource.java
new file mode 100644
index 0000000..5c73956
--- /dev/null
+++ b/examples/multipart-webapp/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartResource.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.multipart.webapp;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.media.multipart.FormDataParam;
+import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
+
+/**
+ * @author Michal Gajdos
+ */
+@Path("/form")
+public class MultiPartResource {
+
+ @POST
+ @Path("part")
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ public String post(@FormDataParam("part") String s) {
+ return s;
+ }
+
+ @POST
+ @Path("part-file-name")
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ public String post(
+ @FormDataParam("part") String s,
+ @FormDataParam("part") FormDataContentDisposition d) {
+ return s + ":" + d.getFileName();
+ }
+
+ @POST
+ @Path("xml-jaxb-part")
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ public String post(
+ @FormDataParam("string") String s,
+ @FormDataParam("string") FormDataContentDisposition sd,
+ @FormDataParam("bean") Bean b,
+ @FormDataParam("bean") FormDataContentDisposition bd) {
+ return s + ":" + sd.getFileName() + "," + b.value + ":" + bd.getFileName();
+ }
+}
diff --git a/examples/multipart-webapp/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MyApplication.java b/examples/multipart-webapp/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MyApplication.java
new file mode 100644
index 0000000..120de05
--- /dev/null
+++ b/examples/multipart-webapp/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MyApplication.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.multipart.webapp;
+
+import javax.ws.rs.ApplicationPath;
+
+import org.glassfish.jersey.media.multipart.MultiPartFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * @author Michal Gajdos
+ */
+@ApplicationPath("/")
+public class MyApplication extends ResourceConfig {
+
+ public MyApplication() {
+ super(MultiPartResource.class, MultiPartFieldInjectedResource.class, MultiPartFeature.class);
+ }
+}
diff --git a/examples/multipart-webapp/src/main/webapp/WEB-INF/web.xml b/examples/multipart-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..f601eea
--- /dev/null
+++ b/examples/multipart-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="2.5"
+ xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+
+ <servlet>
+ <servlet-name>org.glassfish.jersey.examples.multipart.resources.MyApplication</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.glassfish.jersey.examples.multipart.webapp.MyApplication</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>org.glassfish.jersey.examples.multipart.resources.MyApplication</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+</web-app>
diff --git a/examples/multipart-webapp/src/test/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartWebAppTest.java b/examples/multipart-webapp/src/test/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartWebAppTest.java
new file mode 100644
index 0000000..8407396
--- /dev/null
+++ b/examples/multipart-webapp/src/test/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartWebAppTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.multipart.webapp;
+
+import java.io.File;
+import java.net.URI;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.internal.util.SaxHelper;
+import org.glassfish.jersey.internal.util.SimpleNamespaceResolver;
+import org.glassfish.jersey.media.multipart.FormDataBodyPart;
+import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
+import org.glassfish.jersey.media.multipart.FormDataMultiPart;
+import org.glassfish.jersey.media.multipart.MultiPartFeature;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import org.w3c.dom.Document;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for {@code MultipartResource} class.
+ *
+ * @author Naresh (srinivas.bhimisetty at oracle.com)
+ * @author Michal Gajdos
+ */
+public class MultiPartWebAppTest extends JerseyTest {
+
+ @Override
+ protected URI getBaseUri() {
+ return UriBuilder.fromUri(super.getBaseUri()).path("multipart-webapp").build();
+ }
+
+ @Override
+ protected Application configure() {
+ return new MyApplication();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(MultiPartFeature.class);
+ }
+
+ @Test
+ public void testApplicationWadl() throws Exception {
+ final WebTarget target = target().path("application.wadl");
+
+ final Response response = target.request().get();
+ assertEquals(200, response.getStatus());
+ final File tmpFile = response.readEntity(File.class);
+
+ final DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
+ bf.setNamespaceAware(true);
+ bf.setValidating(false);
+
+ if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) {
+ bf.setXIncludeAware(false);
+ }
+
+ final DocumentBuilder b = bf.newDocumentBuilder();
+ final Document d = b.parse(tmpFile);
+
+ final XPath xp = XPathFactory.newInstance().newXPath();
+ xp.setNamespaceContext(new SimpleNamespaceResolver("wadl", "http://wadl.dev.java.net/2009/02"));
+ String val = (String) xp.evaluate(
+ "//wadl:resource[@path='part']/wadl:method[@name='POST']/wadl:request/wadl:representation/@mediaType",
+ d, XPathConstants.STRING);
+
+ assertEquals("multipart/form-data", val);
+ }
+
+ @Test
+ public void testPart() {
+ final WebTarget target = target().path("form/part");
+
+ final FormDataMultiPart mp = new FormDataMultiPart();
+ final FormDataBodyPart p = new FormDataBodyPart(FormDataContentDisposition.name("part").build(), "CONTENT");
+ mp.bodyPart(p);
+
+ final String s = target.request().post(Entity.entity(mp, MediaType.MULTIPART_FORM_DATA_TYPE), String.class);
+ assertEquals("CONTENT", s);
+ }
+
+ @Test
+ public void testPartWithFileName() {
+ final WebTarget target = target().path("form/part-file-name");
+
+ final FormDataMultiPart mp = new FormDataMultiPart();
+ final FormDataBodyPart p = new FormDataBodyPart(FormDataContentDisposition.name("part").fileName("file").build(),
+ "CONTENT");
+ mp.bodyPart(p);
+
+ final String s = target.request().post(Entity.entity(mp, MediaType.MULTIPART_FORM_DATA_TYPE), String.class);
+ assertEquals("CONTENT:file", s);
+ }
+
+ @Test
+ public void testXmlJAXBPart() {
+ final WebTarget target = target().path("form/xml-jaxb-part");
+
+ final FormDataMultiPart mp = new FormDataMultiPart();
+ mp.bodyPart(new FormDataBodyPart(FormDataContentDisposition.name("bean").fileName("bean").build(),
+ new Bean("BEAN"),
+ MediaType.APPLICATION_XML_TYPE));
+ mp.bodyPart(new FormDataBodyPart(FormDataContentDisposition.name("string").fileName("string").build(),
+ "STRING"));
+
+ final String s = target.request().post(Entity.entity(mp, MediaType.MULTIPART_FORM_DATA_TYPE), String.class);
+ assertEquals("STRING:string,BEAN:bean", s);
+ }
+
+ @Test
+ public void testFieldInjectedXmlJAXBPart() {
+ final WebTarget target = target().path("form-field-injected/xml-jaxb-part");
+
+ final FormDataMultiPart mp = new FormDataMultiPart();
+ mp.bodyPart(new FormDataBodyPart(FormDataContentDisposition.name("bean").fileName("bean").build(),
+ new Bean("BEAN"),
+ MediaType.APPLICATION_XML_TYPE));
+ mp.bodyPart(new FormDataBodyPart(FormDataContentDisposition.name("string").fileName("string").build(),
+ "STRING"));
+
+ final String s = target.request().post(Entity.entity(mp, MediaType.MULTIPART_FORM_DATA_TYPE), String.class);
+ assertEquals("STRING:string,BEAN:bean", s);
+ }
+}
diff --git a/examples/oauth-client-twitter/README.MD b/examples/oauth-client-twitter/README.MD
new file mode 100644
index 0000000..ead17fc
--- /dev/null
+++ b/examples/oauth-client-twitter/README.MD
@@ -0,0 +1,80 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+OAuth 1 Client Sample - Twitter Client
+======================================
+
+This example demonstrates how to develop a simple web app client
+utilizing JAX-RS client API and Jersey OAuth support.
+
+Contents
+--------
+
+This example consists of the main class (`App`) and two JAXB beans used
+for unmarshaling the responses from Twitter (`Status` and `User`).
+
+Running the Example
+-------------------
+
+### Registering with Twitter
+
+Before running the example, you have to register your own application
+with Twitter (to obtain consumer key and consumer secret you have to use
+to configure this client). You can do it as follows:
+
+1. Go to [Twitter Developers Page](http://dev.twitter.com/). You need
+ to sign in to the Twitter (if you are new to Twitter you need to
+ sign up). Then in the right upper corner you will see your profile
+ photo and drop down list in which you select "My Application". Click
+ on **Create a new application** button.
+
+2. Fill out the "Create an application" form - you have to pick a
+ unique application name (e.g. app12345678), put in some description,
+ put in something for app website (e.g. http://jersey.java.net),
+ Accept terms by selecting **Yes, I agree** in "Twitter Content"
+ checkbox, enter captcha.
+
+3. Click **Create your twitter application** button.
+
+4. You will be presented with consumer key, consumer secret and other
+ details for your registered application.
+
+### Running for the First Time
+
+Enter the following on the command-line:
+
+> mvn clean compile exec:java -DconsumerKey=<consumer key assigned to your app> -DconsumerSecret=<consumer secret assigned to your app>
+
+### Subsequent Runs
+
+After you run the application for the first time, it stores the consumer
+key, secret and token and token secret in a property file for future
+use. So, for subsequent runs you don't have to specify these as system
+properties. The following command is good enough for running the
+application after it was built, run for the first time and the property
+file was created:
+
+> mvn exec:java
+
+Troubleshooting
+---------------
+
+### HTTP Proxy
+
+Behind a HTTP proxy, you will need to setup the following system
+properties to communicate with the Twitter server:
+
+- http.proxyHost - the name of the proxy host.
+- http.proxyPort - the port of the proxy host.
+- http.proxyAuth - the user name and password to use when connecting
+ to the proxy; this string should be the user name and password
+ separated by a colon (e.g., rickhall:mypassword).
+
+These system properties can be set directly on the command line when
+starting the JVM using the standard "-D<prop>=<value>"
+syntax.
diff --git a/examples/oauth-client-twitter/pom.xml b/examples/oauth-client-twitter/pom.xml
new file mode 100644
index 0000000..8cc99cc
--- /dev/null
+++ b/examples/oauth-client-twitter/pom.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>project</artifactId>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>oauth-client-twitter</artifactId>
+ <packaging>jar</packaging>
+
+
+ <description>
+ Twitter client using OAuth 1 support for Jersey that retrieves Tweets from the home timeline of a
+ registered Twitter account.
+ </description>
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.security</groupId>
+ <artifactId>oauth1-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jackson</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.oauth.twitterclient.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/oauth-client-twitter/src/main/java/org/glassfish/jersey/examples/oauth/twitterclient/App.java b/examples/oauth-client-twitter/src/main/java/org/glassfish/jersey/examples/oauth/twitterclient/App.java
new file mode 100644
index 0000000..f015aa5
--- /dev/null
+++ b/examples/oauth-client-twitter/src/main/java/org/glassfish/jersey/examples/oauth/twitterclient/App.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.oauth.twitterclient;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.Properties;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.oauth1.AccessToken;
+import org.glassfish.jersey.client.oauth1.ConsumerCredentials;
+import org.glassfish.jersey.client.oauth1.OAuth1AuthorizationFlow;
+import org.glassfish.jersey.client.oauth1.OAuth1ClientSupport;
+import org.glassfish.jersey.jackson.JacksonFeature;
+
+/** Simple command-line application that uses Jersey OAuth client library to authenticate
+ * with Twitter.
+ *
+ * @author Martin Matula
+ * @author Miroslav Fuksa
+ */
+public class App {
+ private static final BufferedReader IN = new BufferedReader(new InputStreamReader(System.in, Charset.forName("UTF-8")));
+ private static final String FRIENDS_TIMELINE_URI = "https://api.twitter.com/1.1/statuses/home_timeline.json";
+ private static final Properties PROPERTIES = new Properties();
+ private static final String PROPERTIES_FILE_NAME = "twitterclient.properties";
+ private static final String PROPERTY_CONSUMER_KEY = "consumerKey";
+ private static final String PROPERTY_CONSUMER_SECRET = "consumerSecret";
+ private static final String PROPERTY_TOKEN = "token";
+ private static final String PROPERTY_TOKEN_SECRET = "tokenSecret";
+
+ /**
+ * Main method that creates a {@link Client client} and initializes the OAuth support with
+ * configuration needed to connect to the Twitter and retrieve statuses.
+ * <p/>
+ * Execute this method to demo
+ *
+ * @param args Command line arguments.
+ * @throws Exception Thrown when error occurs.
+ */
+ public static void main(final String[] args) throws Exception {
+ // retrieve consumer key/secret and token/secret from the property file
+ // or system properties
+ loadSettings();
+
+ final ConsumerCredentials consumerCredentials = new ConsumerCredentials(
+ PROPERTIES.getProperty(PROPERTY_CONSUMER_KEY),
+ PROPERTIES.getProperty(PROPERTY_CONSUMER_SECRET));
+
+ final Feature filterFeature;
+ if (PROPERTIES.getProperty(PROPERTY_TOKEN) == null) {
+
+ // we do not have Access Token yet. Let's perfom the Authorization Flow first,
+ // let the user approve our app and get Access Token.
+ final OAuth1AuthorizationFlow authFlow = OAuth1ClientSupport.builder(consumerCredentials)
+ .authorizationFlow(
+ "https://api.twitter.com/oauth/request_token",
+ "https://api.twitter.com/oauth/access_token",
+ "https://api.twitter.com/oauth/authorize")
+ .build();
+ final String authorizationUri = authFlow.start();
+
+ System.out.println("Enter the following URI into a web browser and authorize me:");
+ System.out.println(authorizationUri);
+ System.out.print("Enter the authorization code: ");
+ final String verifier;
+ try {
+ verifier = IN.readLine();
+ } catch (final IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ final AccessToken accessToken = authFlow.finish(verifier);
+
+ // store access token for next application execution
+ PROPERTIES.setProperty(PROPERTY_TOKEN, accessToken.getToken());
+ PROPERTIES.setProperty(PROPERTY_TOKEN_SECRET, accessToken.getAccessTokenSecret());
+
+ // get the feature that will configure the client with consumer credentials and
+ // received access token
+ filterFeature = authFlow.getOAuth1Feature();
+ } else {
+ final AccessToken storedToken = new AccessToken(PROPERTIES.getProperty(PROPERTY_TOKEN),
+ PROPERTIES.getProperty(PROPERTY_TOKEN_SECRET));
+ // build a new feature from the stored consumer credentials and access token
+ filterFeature = OAuth1ClientSupport.builder(consumerCredentials).feature()
+ .accessToken(storedToken).build();
+ }
+
+
+ // create a new Jersey client and register filter feature that will add OAuth signatures and
+ // JacksonFeature that will process returned JSON data.
+ final Client client = ClientBuilder.newBuilder()
+ .register(filterFeature)
+ .register(JacksonFeature.class)
+ .build();
+
+ // make requests to protected resources
+ // (no need to care about the OAuth signatures)
+ final Response response = client.target(FRIENDS_TIMELINE_URI).request().get();
+ if (response.getStatus() != 200) {
+ String errorEntity = null;
+ if (response.hasEntity()) {
+ errorEntity = response.readEntity(String.class);
+ }
+ throw new RuntimeException("Request to Twitter was not successful. Response code: "
+ + response.getStatus() + ", reason: " + response.getStatusInfo().getReasonPhrase()
+ + ", entity: " + errorEntity);
+ }
+
+ // persist the current consumer key/secret and token/secret for future use
+ storeSettings();
+
+ final List<Status> statuses = response.readEntity(new GenericType<List<Status>>() {
+ });
+
+ System.out.println("Tweets:\n");
+ for (final Status s : statuses) {
+ System.out.println(s.getText());
+ System.out.println("[posted by " + s.getUser().getName() + " at " + s.getCreatedAt() + "]");
+ }
+
+
+ }
+
+ private static void loadSettings() {
+ FileInputStream st = null;
+ try {
+ st = new FileInputStream(PROPERTIES_FILE_NAME);
+ PROPERTIES.load(st);
+ } catch (final IOException e) {
+ // ignore
+ } finally {
+ if (st != null) {
+ try {
+ st.close();
+ } catch (final IOException ex) {
+ // ignore
+ }
+ }
+ }
+
+ for (final String name : new String[]{PROPERTY_CONSUMER_KEY, PROPERTY_CONSUMER_SECRET,
+ PROPERTY_TOKEN, PROPERTY_TOKEN_SECRET}) {
+ final String value = System.getProperty(name);
+ if (value != null) {
+ PROPERTIES.setProperty(name, value);
+ }
+ }
+
+ if (PROPERTIES.getProperty(PROPERTY_CONSUMER_KEY) == null
+ || PROPERTIES.getProperty(PROPERTY_CONSUMER_SECRET) == null) {
+ System.out.println("No consumerKey and/or consumerSecret found in twitterclient.properties file. "
+ + "You have to provide these as system properties. See README.html for more information.");
+ System.exit(1);
+ }
+ }
+
+ private static void storeSettings() {
+ FileOutputStream st = null;
+ try {
+ st = new FileOutputStream(PROPERTIES_FILE_NAME);
+ PROPERTIES.store(st, null);
+ } catch (final IOException e) {
+ // ignore
+ } finally {
+ try {
+ if (st != null) {
+ st.close();
+ }
+ } catch (final IOException ex) {
+ // ignore
+ }
+ }
+ }
+
+}
diff --git a/examples/oauth-client-twitter/src/main/java/org/glassfish/jersey/examples/oauth/twitterclient/Status.java b/examples/oauth-client-twitter/src/main/java/org/glassfish/jersey/examples/oauth/twitterclient/Status.java
new file mode 100644
index 0000000..34f2abe
--- /dev/null
+++ b/examples/oauth-client-twitter/src/main/java/org/glassfish/jersey/examples/oauth/twitterclient/Status.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.oauth.twitterclient;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/**
+ * Entity into which the Twitter status (tweet) is deserialized
+ *
+ * @author Martin Matula
+ */
+@XmlRootElement
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Status {
+
+ @XmlElement(name = "created_at")
+ private String createdAt;
+ @XmlElement(name = "text")
+ private String text;
+ @XmlElement(name = "user")
+ private User user;
+
+ public String getCreatedAt() {
+ return createdAt;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public User getUser() {
+ return user;
+ }
+}
diff --git a/examples/oauth-client-twitter/src/main/java/org/glassfish/jersey/examples/oauth/twitterclient/User.java b/examples/oauth-client-twitter/src/main/java/org/glassfish/jersey/examples/oauth/twitterclient/User.java
new file mode 100644
index 0000000..9be2571
--- /dev/null
+++ b/examples/oauth-client-twitter/src/main/java/org/glassfish/jersey/examples/oauth/twitterclient/User.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.oauth.twitterclient;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/**
+ * Entity into which the Twitter user is deserialized.
+ *
+ * @author Martin Matula
+ */
+@XmlRootElement
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class User {
+ @XmlElement(name = "name")
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/examples/open-tracing/README.MD b/examples/open-tracing/README.MD
new file mode 100644
index 0000000..7fa3a82
--- /dev/null
+++ b/examples/open-tracing/README.MD
@@ -0,0 +1,84 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Jersey OpenTracing Example
+==========================
+
+This example demonstrates Jersey OpenTracing integration. The JAX-RS resource contains several resource method showing various
+possibilities.
+
+This particular example is configured with Jaeger as the GlobalTracer, but can be easily adjusted to use any other tracer
+implementation.
+
+Running the Example
+-------------------
+Example can be launched as is, but does not perform any interesting action out of the box.
+To be able to visualise the traces, it is recommended to start Jaeger (and th UI) locally, e.g. in Docker:
+
+> docker run -d -p 5775:5775/udp -p 16686:16686 jaegertracing/all-in-one:travis-1278
+
+Check the UI on [localhost:16686](http://localhost:16686), there should be no traces visible.
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
+
+Try to access resources using the URIs listed bellow and look into Jager UI what traces are produced.
+The first example should be visible in the Jaeger UI right after the example application is started. Others can be created by
+doing rest calls to the exposed resource.
+
+1. On start, the example application sends one request demonstrating more complex tracing
+- jersey client (w/ registered OpenTracingFeature) creates the "jersey-client-GET" span
+- the span is propagated to server (via http headers), jersey server (w/ registered OpenTracinfFeature) automatically creates a
+ child span of the client span
+- in the resource method, child span of the server span is manually created and propagated into managed client calls
+- the managed clients are also tracing-enabled, so each one creates its own child span, that is propagated to the server, and
+so on
+
+2. No explicit (user-defined) spans.
+- one automatically created "root" span, that measures the processing on the Jersey side and one "resource-level" span to be
+used for logging events on the application level
+- the "root" contains several tags with request and response metadata, such as request headers, status code, etc
+- also, several technical Jersey-level events have been logged
+- to see this simple case, call
+> curl localhost:8080/opentracing/resource/defaultTrace
+
+3. Explicit logging into resource-level span
+- same as above, but the "resource-level" span was resolved within the application logic, an event was logged and a tag was added
+> curl localhost:8080/opentracing/resource/appLevelLogging
+
+- similar call with POST:
+> curl -X POST -d "Jersey Rocks" localhost:8080/opentracing/resource/appLevelPost
+
+
+4. Explicit child span creation
+- same as above, but instead of "resource-level" span, a child span was created and used for logging and adding tags
+- note, that such a span needs to be finished manually, otherwise it won't propagate (the tracing info will be lost)
+> curl localhost:8080/opentracing/resource/childSpan
+
+
+4. Client calls from the resource
+- more complex case, the resource method invokes two client calls using managed client
+- one request via managed client is triggered within the "provided", resource-level span
+- child span is created and another request via manged client is triggered within the child span
+- the span context needs to be propagated manually using the OpenTracingFeature.SPAN_CONTEXT_PROPERTY
+- in both cases, the span context is propagated using http headers to the server and back to the client
+- the child span (created in the resource) needs to be explicitly finished in the resource (or elsewhere, but needs to be finished)
+> curl localhost:8080/opentracing/resource/traceWithManagedClient
+
+5. Asynchronous processing
+- either basic case, but using asynchronous request processing
+- there should be no practical difference visible in the tracing info
+> curl localhost:8080/opentracing/resource/async
+
+6. Failing resource
+- demonstrates exception thrown in the resource method
+- Jaeger shows failed spans with the exclamation mark symbol and the exception is logged in the request span
+> curl localhost:8080/opentracing/resource/error
\ No newline at end of file
diff --git a/examples/open-tracing/pom.xml b/examples/open-tracing/pom.xml
new file mode 100644
index 0000000..d3083e3
--- /dev/null
+++ b/examples/open-tracing/pom.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>open-tracing</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-open-tracing</name>
+
+ <description>Jersey OpenTracing example</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.incubator</groupId>
+ <artifactId>jersey-open-tracing</artifactId>
+ <!-- not a part of bom pom.xml as long as it resides in incubator -->
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.opentracing</groupId>
+ <artifactId>opentracing-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>io.opentracing</groupId>
+ <artifactId>opentracing-util</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.uber.jaeger</groupId>
+ <artifactId>jaeger-core</artifactId>
+ <version>0.20.0</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-util</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.opentracing.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/open-tracing/src/main/java/org/glassfish/jersey/examples/opentracing/App.java b/examples/open-tracing/src/main/java/org/glassfish/jersey/examples/opentracing/App.java
new file mode 100644
index 0000000..b7c48e5
--- /dev/null
+++ b/examples/open-tracing/src/main/java/org/glassfish/jersey/examples/opentracing/App.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.opentracing;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.opentracing.OpenTracingFeature;
+import org.glassfish.jersey.opentracing.OpenTracingUtils;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+import com.uber.jaeger.Configuration;
+
+import io.opentracing.Span;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Open tracing example application.
+ * <p>
+ * Exposes OpenTracing-enabled REST application (with Jaeger registered as the Tracer)
+ * and invokes one request from (also OpenTracing-enabled) Jersey client.
+ * <p>
+ * To visualise the traces, start Jaeger locally:
+ * <pre>
+ * docker run -d -p 5775:5775/udp -p 16686:16686 jaegertracing/all-in-one:travis-1278
+ * </pre>
+ * and go to {@code localhost:16686}.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/opentracing");
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Hello World\" Jersey OpenTracing Example App");
+ prepare();
+
+ final ResourceConfig resourceConfig = new ResourceConfig(TracedResource.class,
+ OpenTracingFeature.class,
+ ReqFilterA.class, ReqFilterB.class,
+ RespFilterA.class, RespFilterB.class);
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, false);
+ Runtime.getRuntime().addShutdownHook(new Thread(server::shutdownNow));
+ server.start();
+
+ System.out.println(String.format("Application started.\nTry out %s/application.wadl\n"
+ + "Stop the application using CTRL+C", BASE_URI));
+
+ // do the first "example" request with tracing-enabled client to show something in Jaegger UI,
+ // include some weird headers and accepted types, that will be visible in the span's tags
+ Client client = ClientBuilder.newBuilder().register(OpenTracingFeature.class).build();
+ client.target(BASE_URI).path("resource/managedClient").request()
+ .accept("text/plain", "application/json", "*/*")
+ .header("foo", "bar")
+ .header("foo", "baz")
+ .header("Hello", "World").get();
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Configures Jaeger tracer as the {@link GlobalTracer}.
+ */
+ private static void prepare() {
+ GlobalTracer.register(
+ new com.uber.jaeger.Configuration(
+ "OpenTracingTemporaryTest",
+ new Configuration.SamplerConfiguration("const", 1),
+ new Configuration.ReporterConfiguration(
+ true,
+ "localhost",
+ 5775,
+ 1000,
+ 10000)
+ ).getTracer()
+ );
+ }
+
+ /**
+ * No-op request filter, just to test, that it will be listed in the root level span's logs.
+ * For demonstrating purposes, it resolves the "root" request span and logs into it. If it fails to resolve the span, it
+ * creates a new ad-hoc span.
+ */
+ static class ReqFilterA implements ContainerRequestFilter {
+ @Override
+ public void filter(ContainerRequestContext requestContext) throws IOException {
+ Span span = OpenTracingUtils
+ .getRequestSpan(requestContext)
+ .orElse(GlobalTracer.get().buildSpan("ad-hoc-span-reqA").startManual());
+ span.log("ReqFilterA.filter() invoked");
+ }
+ }
+
+ /**
+ * No-op request filter, just to test, that it will be listed in the root level span's logs.
+ * For demonstrating purposes, it resolves the "root" request span and logs into it. If it fails to resolve the span, it
+ * creates a new ad-hoc span.
+ */
+ static class ReqFilterB implements ContainerRequestFilter {
+ @Override
+ public void filter(ContainerRequestContext requestContext) throws IOException {
+ Span span = OpenTracingUtils
+ .getRequestSpan(requestContext)
+ .orElse(GlobalTracer.get().buildSpan("ad-hoc-span-reqB").startManual());
+ span.log("ReqFilterB.filter() invoked");
+ }
+ }
+
+ /**
+ * No-op response filter, just to test, that it will be listed in the root level span's logs.
+ */
+ static class RespFilterA implements ContainerResponseFilter {
+ @Override
+ public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
+ Span span = OpenTracingUtils
+ .getRequestSpan(requestContext)
+ .orElse(GlobalTracer.get().buildSpan("ad-hoc-span-respA").startManual());
+ span.log("RespFilterA.filter() invoked");
+ }
+ }
+
+ /**
+ * No-op response filter, just to test, that it will be listed in the root level span's logs.
+ */
+ static class RespFilterB implements ContainerResponseFilter {
+ @Override
+ public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
+ Span span = OpenTracingUtils
+ .getRequestSpan(requestContext)
+ .orElse(GlobalTracer.get().buildSpan("ad-hoc-span-respB").startManual());
+ span.log("RespFilterB.filter() invoked");
+ }
+ }
+}
+
diff --git a/examples/open-tracing/src/main/java/org/glassfish/jersey/examples/opentracing/TracedResource.java b/examples/open-tracing/src/main/java/org/glassfish/jersey/examples/opentracing/TracedResource.java
new file mode 100644
index 0000000..ce8e83b
--- /dev/null
+++ b/examples/open-tracing/src/main/java/org/glassfish/jersey/examples/opentracing/TracedResource.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.opentracing;
+
+import java.util.concurrent.Executors;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.opentracing.OpenTracingFeature;
+import org.glassfish.jersey.opentracing.OpenTracingUtils;
+import org.glassfish.jersey.server.ManagedAsync;
+import org.glassfish.jersey.server.Uri;
+
+import io.opentracing.Span;
+
+
+/**
+ * OpenTracing example resource.
+ * <p>
+ * Jersey (with registered {@link OpenTracingFeature} will automatically
+ * create and start span for each request ("root" span or "request" span) and a child span to be used in the resource method
+ * ("resource" span). The root span is used for Jersey-level event logging (resource matching started, request filters applied,
+ * etc). The resource span serves for application-level event logging purposes (used-defined). Both are automatically created
+ * and also automatically finished.
+ * <p>
+ * Resource span is created right before the resource method invocation and finished right after resource method finishes. It
+ * can be resolved by calling {@link OpenTracingUtils#getRequestSpan(ContainerRequestContext)}.
+ * <p>
+ * Application code can also create ad-hoc spans as child spans of the resource span. This can be achieved by calling one of the
+ * convenience methods {@link OpenTracingUtils#getRequestChildSpan(ContainerRequestContext)}.
+ * <p>
+ * {@link ContainerRequestContext} can be obtained via injection.
+ * <p>
+ * All the ad-hoc created spans MUST be {@link Span#finish() finished} explicitly.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@Path(value = "/resource")
+public class TracedResource {
+
+ /**
+ * Resource method with no explicit tracing.
+ * <p>
+ * One span (jersey-server) will be created and finished automatically.
+ *
+ * @return dummy response
+ */
+ @GET
+ @Path("defaultTrace")
+ public Response defaultTrace() {
+ return Response.ok("foo").build();
+ }
+
+ /**
+ * Resource method with explicit logging into resource span.
+ *
+ * @param context injected request context with resource-level span reference
+ * @return dummy response
+ * @throws InterruptedException if interrupted
+ */
+ @GET
+ @Path("appLevelLogging")
+ public Response appLevelLogging(@Context ContainerRequestContext context) throws InterruptedException {
+ final Span resourceSpan = OpenTracingUtils
+ .getRequestSpan(context)
+ .orElseThrow(() -> new RuntimeException("Tracing has failed"));
+
+ resourceSpan.log("Starting expensive operation.");
+ // Do the business
+ Thread.sleep(200);
+ resourceSpan.log("Expensive operation finished.");
+ resourceSpan.setTag("expensiveOperationSuccess", true);
+
+ return Response.ok("SUCCESS").build();
+ }
+
+ /**
+ * Similar as {@link #appLevelLogging(ContainerRequestContext)}, just with {@code POST} method.
+ *
+ * @param entity posted entity
+ * @param context injected context
+ * @return dummy response
+ */
+ @POST
+ @Path("appLevelPost")
+ public Response tracePost(String entity, @Context ContainerRequestContext context) {
+ final Span resourceSpan = OpenTracingUtils
+ .getRequestSpan(context)
+ .orElseThrow(() -> new RuntimeException("Tracing has failed"));
+
+ resourceSpan.setTag("result", "42");
+ resourceSpan.setBaggageItem("entity", entity);
+ return Response.ok("Done!").build();
+ }
+
+ /**
+ * Resource method with explicit child span creation.
+ *
+ * @param context injected request context with resource-level (parent) span reference
+ * @return dummy response
+ * @throws InterruptedException if interrupted
+ */
+ @GET
+ @Path("childSpan")
+ public Response childSpan(@Context ContainerRequestContext context) throws InterruptedException {
+ final Span childSpan = OpenTracingUtils.getRequestChildSpan(context, "AppCreatedSpan");
+ childSpan.log("Starting expensive operation.");
+ // Do the business
+ Thread.sleep(200);
+ childSpan.log("Expensive operation finished.");
+ childSpan.setTag("expensiveOperationSuccess", true);
+
+ childSpan.finish();
+ return Response.ok("SUCCESS").build();
+ }
+
+
+ /**
+ * Resource method with explicit span creation and propagation into injected managed client.
+ * <p>
+ * Shows how to propagate the server-side span into managed client (or any common Jersey client).
+ * This way, the client span will be child of the resource span.
+ *
+ * @param context injected context
+ * @param wt injected web target
+ * @return dummy response
+ */
+ @GET
+ @Path("managedClient")
+ public Response traceWithManagedClient(@Context ContainerRequestContext context,
+ @Uri("resource/appLevelPost") WebTarget wt) {
+ final Span providedSpan = OpenTracingUtils
+ .getRequestSpan(context)
+ .orElseThrow(() -> new RuntimeException("Tracing failed"));
+
+ providedSpan.log("Resource method started.");
+
+ final Response response = wt.request()
+ .property(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, providedSpan.context()) // <--- span propagation
+ .post(Entity.text("Hello"));
+
+ providedSpan.log("1st Response received from managed client");
+ providedSpan.log("Firing 1st request from managed client");
+
+ providedSpan.log("Creating child span");
+ final Span childSpan = OpenTracingUtils.getRequestChildSpan(context, "jersey-resource-child-span");
+
+
+ childSpan.log("Firing 2nd request from managed client");
+ final Response response2 = wt.request()
+ .property(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, childSpan.context()) // <--- span propagation
+ .post(Entity.text("World"));
+ childSpan.log("2st Response received from managed client");
+
+ childSpan.finish();
+ return Response.ok("Result: " + response.getStatus() + ", " + response2.getStatus()).build();
+ }
+
+ @GET
+ @Path("async")
+ @ManagedAsync
+ public void traceWithAsync(@Suspended final AsyncResponse asyncResponse, @Context ContainerRequestContext context) {
+ final Span span = OpenTracingUtils.getRequestSpan(context).orElseThrow(() -> new RuntimeException("tracing failed"));
+ span.log("In the resource method.");
+ Executors.newSingleThreadExecutor().submit(() -> {
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ span.log("Interrupted");
+ e.printStackTrace();
+ }
+ span.log("Resuming");
+ asyncResponse.resume("OK");
+ });
+ span.log("Before exiting the resource method");
+ }
+
+ @GET
+ @Path("error")
+ public String failTrace(@Context ContainerRequestContext context) {
+ throw new RuntimeException("Failing just for fun.");
+ }
+}
diff --git a/examples/osgi-helloworld-webapp/README.MD b/examples/osgi-helloworld-webapp/README.MD
new file mode 100644
index 0000000..40b98f6
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/README.MD
@@ -0,0 +1,47 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+HelloWorld OSGi Example
+=======================
+
+This example demonstrates how to develop a simple OSGi WAR bundle
+containing a RESTful hello world web service
+
+Contents
+--------
+
+The example WAR (see the `war-bundle` module) consists of two Jersey
+resources:
+
+`org.glassfish.jersey.examples.osgi.helloworld.resource.HelloWorldResource`
+
+that produces a textual response to an HTTP GET
+
+`org.glassfish.jersey.examples.osgi.helloworld.resource.AnotherResource`
+
+that produces a different textual response to an HTTP GET. The
+purpose of this resource is to show how to define multiple web
+resources within a web application.
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP method
+------------------ | -------------------- | -------------
+**_/helloworld_** | HelloWorldResource | GET
+**_/another_** | AnotherResource | GET
+
+Running the Example
+-------------------
+
+To run the example, you would need to build the WAR file and install it
+to an OSGi runtime (e.g. Apache Felix) together with other OSGi modules.
+Look at the attached `functional-test` module for details on the
+programatical runtime configuration. To build the war archive and run
+the tests, you can just launch
+
+> mvn clean install
diff --git a/examples/osgi-helloworld-webapp/additional-bundle/pom.xml b/examples/osgi-helloworld-webapp/additional-bundle/pom.xml
new file mode 100644
index 0000000..3e0fa05
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/additional-bundle/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>osgi-helloworld-webapp</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.examples.osgi-helloworld-webapp</groupId>
+ <artifactId>additional-bundle</artifactId>
+ <name>jersey-examples-osgi-helloworld-webapp-additional</name>
+
+ <description>OSGi Helloworld Webapp - additional bundle</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- to generate the MANIFEST-FILE required by the bundle -->
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <manifestLocation>${project.build.directory}/META-INF</manifestLocation>
+ <supportedProjectTypes>
+ <supportedProjectType>bundle</supportedProjectType>
+ <supportedProjectType>jar</supportedProjectType>
+ </supportedProjectTypes>
+ <instructions>
+ <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+ <Import-Package>*</Import-Package>
+ <Export-Package>org.glassfish.jersey.examples.osgi.helloworld.additional.resource</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <!-- do not create source zip bundles -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <skipAssembly>true</skipAssembly>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/osgi-helloworld-webapp/additional-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/additional/resource/AdditionalResource.java b/examples/osgi-helloworld-webapp/additional-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/additional/resource/AdditionalResource.java
new file mode 100644
index 0000000..c18d5c4
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/additional-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/additional/resource/AdditionalResource.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgi.helloworld.additional.resource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * Test resource for osgi-helloworld-webapp example;
+ * It is aimed to ensure, that the package scanning works in OSGi for multiple packages defined in web.xml
+ *
+ * There is also an alternate version of the resource with the same class name within the same package which
+ * should not be visible to Jersey via OSGi.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@Path("/additional")
+public class AdditionalResource {
+ @GET
+ @Produces("text/plain")
+ public String getAdditionalResourceMessage() {
+ return "Additional Bundle!";
+ }
+}
diff --git a/examples/osgi-helloworld-webapp/additional-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/additional/resource/subpackage/AdditionalSubPackagedResource.java b/examples/osgi-helloworld-webapp/additional-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/additional/resource/subpackage/AdditionalSubPackagedResource.java
new file mode 100644
index 0000000..35318cc
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/additional-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/additional/resource/subpackage/AdditionalSubPackagedResource.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgi.helloworld.additional.resource.subpackage;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * This resource is located in a sub-package and will be detected by OSGI framework only if recursive scanning is turned on.
+ *
+ * @author Stepan Vavra (stepan.vavra at oracle.com)
+ */
+@Path("/subadditional")
+public class AdditionalSubPackagedResource {
+
+ @GET
+ @Produces("text/plain")
+ public String getSubPackagedAdditionalMessage() {
+ return "Sub-packaged Additional Bundle!";
+ }
+
+}
diff --git a/examples/osgi-helloworld-webapp/alternate-version-bundle/pom.xml b/examples/osgi-helloworld-webapp/alternate-version-bundle/pom.xml
new file mode 100644
index 0000000..d958fdc
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/alternate-version-bundle/pom.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>osgi-helloworld-webapp</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.examples.osgi-helloworld-webapp</groupId>
+ <artifactId>alternate-version-bundle</artifactId>
+ <name>jersey-examples-osgi-helloworld-webapp-alternate-version</name>
+
+ <description>OSGi Helloworld Webapp - alternate version bundle</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- to generate the MANIFEST-FILE required by the bundle -->
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <manifestLocation>${project.build.directory}/META-INF</manifestLocation>
+ <supportedProjectTypes>
+ <supportedProjectType>bundle</supportedProjectType>
+ <supportedProjectType>jar</supportedProjectType>
+ </supportedProjectTypes>
+ <instructions>
+ <Bundle-SymbolicName>additional-bundle</Bundle-SymbolicName>
+ <Bundle-Name>jersey-examples-osgi-helloworld-webapp-additional</Bundle-Name>
+ <!-- this version has to be different from the 'additional-bundle'
+ version in order to test the correct bundle resolution within OSGi -->
+ <Bundle-Version>2.2.1</Bundle-Version>
+ <Import-Package>*</Import-Package>
+ <Export-Package>org.glassfish.jersey.examples.osgi.helloworld.additional.resource</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <!-- do not create source zip bundles -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <skipAssembly>true</skipAssembly>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/osgi-helloworld-webapp/alternate-version-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/additional/resource/AdditionalResource.java b/examples/osgi-helloworld-webapp/alternate-version-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/additional/resource/AdditionalResource.java
new file mode 100644
index 0000000..214b37f
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/alternate-version-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/additional/resource/AdditionalResource.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgi.helloworld.additional.resource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * Alternate version of test resource for osgi-helloworld-webapp example;
+ * The original class within additional-bundle module has the same name and resides withing the same package.
+ *
+ * The containing module's (alternate-version-bundle) pom.xml configures the MANIFEST bundle headers as an older version of
+ * additional-bundle.
+ *
+ * Both versions are then explicitly loaded into OSGi runtime and the correct version (the one in additional-bundle module)
+ * should be used by Jersey.
+ *
+ * If this version of the resource is used, the test will fail.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@Path("/additional")
+public class AdditionalResource {
+ @GET
+ @Produces("text/plain")
+ public String getAdditionalResourceMessage() {
+ return "WRONG VERSION of additional Bundle!";
+ }
+}
diff --git a/examples/osgi-helloworld-webapp/functional-test/pom.xml b/examples/osgi-helloworld-webapp/functional-test/pom.xml
new file mode 100644
index 0000000..de4aeaf
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/functional-test/pom.xml
@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>osgi-helloworld-webapp</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.examples.osgi-helloworld-webapp</groupId>
+ <artifactId>functional-test</artifactId>
+ <packaging>pom</packaging>
+ <name>jersey-examples-osgi-helloworld-webapp-test</name>
+
+ <dependencies>
+ <!-- example war bundle -->
+ <dependency>
+ <groupId>org.glassfish.jersey.examples.osgi-helloworld-webapp</groupId>
+ <artifactId>war-bundle</artifactId>
+ <type>war</type>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.examples.osgi-helloworld-webapp</groupId>
+ <artifactId>additional-bundle</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.examples.osgi-helloworld-webapp</groupId>
+ <artifactId>alternate-version-bundle</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>aopalliance-repackaged</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.javassist</groupId>
+ <artifactId>javassist</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- logging -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <version>1.6.4</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- JUnit and Pax-Exam dependencies -->
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-junit4</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-container-forked</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-link-mvn</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.web</groupId>
+ <artifactId>pax-web-jetty-bundle</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.web</groupId>
+ <artifactId>pax-web-extender-war</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- OSGi runtime - Felix -->
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.eventadmin</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework.security</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <!-- uncomment the following dependency to get ability
+ to run felix console in the test -->
+ <!--dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.shell.remote</artifactId>
+ <version>1.1.2</version>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <artifactId>org.osgi.core</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency-->
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>maven-paxexam-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>generate-config</id>
+ <goals>
+ <goal>generate-depends-file</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>test-compile</id>
+ <phase>test-compile</phase>
+ <goals>
+ <goal>testCompile</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>process-test-resources</phase>
+ <goals>
+ <goal>testResources</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkMode>always</forkMode>
+ <enableAssertions>false</enableAssertions>
+ <classpathDependencyExcludes>
+ <classpathDepenencyExclude>org.apache.felix:org.osgi.core</classpathDepenencyExclude>
+ <classpathDepenencyExclude>org.osgi:org.osgi.core</classpathDepenencyExclude>
+ </classpathDependencyExcludes>
+ </configuration>
+ <executions>
+ <execution>
+ <id>test</id>
+ <phase>test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <!-- do not create source zip bundles -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <skipAssembly>true</skipAssembly>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>sonar</id>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <!-- disable JaCoCo listener because it's not working with <forkMode>always</fork> (<reuseForks>false</reuseForks> respectively)
+ https://jira.sonarsource.com/browse/SONARJAVA-728 (https://github.com/SonarSource/sonar-java/pull/324) -->
+ <properties combine.self="override" />
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
+ </profiles>
+
+ <properties>
+ <surefire.security.argline>-Dfelix.policy=${project.build.directory}/test-classes/felix.policy -Druntime.policy=${project.build.directory}/test-classes/runtime.policy</surefire.security.argline>
+ </properties>
+
+</project>
diff --git a/examples/osgi-helloworld-webapp/functional-test/src/test/java/org/glassfish/jersey/examples/helloworld/test/AbstractWebAppTest.java b/examples/osgi-helloworld-webapp/functional-test/src/test/java/org/glassfish/jersey/examples/helloworld/test/AbstractWebAppTest.java
new file mode 100644
index 0000000..86bd28a
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/functional-test/src/test/java/org/glassfish/jersey/examples/helloworld/test/AbstractWebAppTest.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.test;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.URI;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.UriBuilder;
+
+import javax.inject.Inject;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
+import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionUpdate;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+import static org.junit.Assert.assertNotNull;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public abstract class AbstractWebAppTest {
+
+ @Inject
+ BundleContext bundleContext;
+ /**
+ * maximum waiting time for runtime initialization and Jersey deployment
+ */
+ public static final long MAX_WAITING_SECONDS = 10L;
+ /**
+ * Latch for blocking the testing thread until the runtime is ready and
+ * Jersey deployed
+ */
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ private static final int port = getProperty("jersey.config.test.container.port", 8080);
+ private static final String runtimePolicy = AccessController.doPrivileged(
+ PropertiesHelper.getSystemProperty("runtime.policy"));
+ private static final String felixPolicy = AccessController.doPrivileged(PropertiesHelper.getSystemProperty("felix.policy"));
+ private static final String CONTEXT = "/helloworld";
+ private static final URI baseUri = UriBuilder.fromUri("http://localhost").port(port).path(CONTEXT).build();
+ private static final Logger LOGGER = Logger.getLogger(AbstractWebAppTest.class.getName());
+
+ /**
+ * Allow subclasses to define additional OSGi configuration - called after
+ * genericOsgiOptions() and jettyOptions()
+ *
+ * @return list of pax exam Options
+ */
+ public abstract List<Option> osgiRuntimeOptions();
+
+ /**
+ * Generic OSGi options - defines which dependencies (bundles) should be
+ * loaded into runtime
+ */
+ public List<Option> genericOsgiOptions() {
+
+ // uncomment for debugging using felix console (lookup gogo string in the commnented lines below)
+ String gogoVersion = "0.8.0";
+
+ @SuppressWarnings("RedundantStringToString")
+ List<Option> options = Arrays.asList(options(
+ // vmOption("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"),
+ // vmOption("-Djava.security.debug=scl"),
+
+ // uncomment for verbose class loading info
+ // vmOption("-verbose:class"),
+
+ // bootDelegationPackage("org.glassfish.jersey.client.*"),
+
+ systemProperty("java.security.manager").value(""),
+ systemProperty("felix.policy").value(felixPolicy),
+ systemProperty("java.security.policy").value(runtimePolicy),
+ systemProperty(org.osgi.framework.Constants.FRAMEWORK_SECURITY)
+ .value(org.osgi.framework.Constants.FRAMEWORK_SECURITY_OSGI),
+ systemProperty("org.osgi.service.http.port").value(String.valueOf(port)),
+ systemProperty("org.osgi.framework.system.packages.extra").value("javax.annotation"),
+ systemProperty("jersey.config.test.container.port").value(String.valueOf(port)),
+ // systemProperty(BundleLocationProperty).value(bundleLocation),
+
+ // do not remove the following line
+ systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("FINEST"),
+ // uncomment the following 4 lines should you need to debug from th felix console
+ // mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.gogo.runtime").version(gogoVersion),
+ // mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.gogo.shell").version(gogoVersion),
+ // mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.gogo.command").version(gogoVersion),
+ // mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.shell.remote").versionAsInProject(),
+ mavenBundle("org.apache.felix", "org.apache.felix.framework.security").versionAsInProject(),
+ // uncomment for logging (do not remove the following two lines)
+ // mavenBundle("org.ops4j.pax.logging", "pax-logging-api", "1.4"),
+ // mavenBundle("org.ops4j.pax.logging", "pax-logging-service", "1.4"),
+
+ // javax.annotation must go first!
+ mavenBundle().groupId("javax.annotation").artifactId("javax.annotation-api").versionAsInProject(),
+ // pax exam dependencies
+ mavenBundle("org.ops4j.pax.url", "pax-url-mvn"),
+ junitBundles(), // adds junit classes to the OSGi context
+
+ // HK2
+ mavenBundle().groupId("org.glassfish.hk2").artifactId("hk2-api").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2").artifactId("osgi-resource-locator").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2").artifactId("hk2-locator").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2").artifactId("hk2-utils").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2.external").artifactId("javax.inject").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2.external").artifactId("aopalliance-repackaged").versionAsInProject(),
+ mavenBundle().groupId("org.javassist").artifactId("javassist").versionAsInProject(),
+ // JAX-RS API
+ mavenBundle().groupId("javax.ws.rs").artifactId("javax.ws.rs-api").versionAsInProject(),
+ // validation - required by jersey-container-servlet-core
+ mavenBundle().groupId("javax.validation").artifactId("validation-api").versionAsInProject(),
+ // Jersey bundles
+ mavenBundle().groupId("org.glassfish.jersey.core").artifactId("jersey-common").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.jersey.core").artifactId("jersey-server").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.jersey.core").artifactId("jersey-client").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.jersey.containers").artifactId("jersey-container-servlet-core")
+ .versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.jersey.inject").artifactId("jersey-hk2").versionAsInProject(),
+ // Those two bundles have different (unique) maven coordinates, but represent the same OSGi bundle in two
+ // different versions.
+ // (see the maven bundle plugin configuration in each of the two pom.xml files
+ // Both bundles are explicitly loaded here to ensure, that both co-exist within the OSGi runtime;
+ mavenBundle().groupId("org.glassfish.jersey.examples.osgi-helloworld-webapp")
+ .artifactId("additional-bundle").versionAsInProject(),
+ // The alternate-version-bundle contains the same resource in the same package
+ // (org.glassfish.jersey.examples.osgi.helloworld.additional.resource.AdditionalResource),
+ // mapped to the same URI (/additional), but returning a different string as a response.
+ // ---> if the test passes, it ensures, that Jersey sees/uses the correct version of the bundle
+ mavenBundle().groupId("org.glassfish.jersey.examples.osgi-helloworld-webapp")
+ .artifactId("alternate-version-bundle").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.jersey.examples.osgi-helloworld-webapp")
+ .artifactId("war-bundle").type("war").versionAsInProject()
+ // uncomment for debugging
+ // vmOption( "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005" )
+ ));
+
+ final String localRepository = AccessController.doPrivileged(PropertiesHelper.getSystemProperty("localRepository"));
+ if (localRepository != null) {
+ options = new ArrayList<Option>(options);
+ options.add(systemProperty("org.ops4j.pax.url.mvn.localRepository").value(localRepository));
+ }
+
+ return options;
+ }
+
+ public List<Option> jettyOptions() {
+ return Arrays.asList(options(
+ mavenBundle().groupId("org.ops4j.pax.web").artifactId("pax-web-jetty-bundle").versionAsInProject(),
+ mavenBundle().groupId("org.ops4j.pax.web").artifactId("pax-web-extender-war").versionAsInProject()));
+ }
+
+ private void updatePermissionsFromFile() throws IOException {
+
+ final ServiceReference cpaRef = bundleContext.getServiceReference(ConditionalPermissionAdmin.class.getName());
+ final ConditionalPermissionAdmin conditionalPermissionAdmin = (ConditionalPermissionAdmin) bundleContext
+ .getService(cpaRef);
+ final ConditionalPermissionUpdate permissionUpdate = conditionalPermissionAdmin.newConditionalPermissionUpdate();
+ final List conditionalPermissionInfos = permissionUpdate.getConditionalPermissionInfos();
+
+ try {
+
+ final BufferedReader reader = new BufferedReader(new FileReader(felixPolicy));
+ String line;
+ final Set<String> cpiNames = new HashSet<String>();
+
+ while ((line = reader.readLine()) != null) {
+ if (!line.startsWith("//")) {
+ final ConditionalPermissionInfo cpi = conditionalPermissionAdmin.newConditionalPermissionInfo(line);
+ final String cpiName = cpi.getName();
+ if (cpiNames.contains(cpiName)) {
+ throw new RuntimeException("Redundant policy name!");
+ }
+ cpiNames.add(cpiName);
+ conditionalPermissionInfos.add(cpi);
+ }
+ }
+ reader.close();
+ permissionUpdate.commit();
+
+ } finally {
+ bundleContext.ungetService(cpaRef);
+ }
+ }
+
+ /**
+ * After the war bundle is loaded and initialized, it sends custom OSGi
+ * event "jersey/test/DEPLOYED"; This class handles the event (releases the
+ * waiting lock)
+ */
+ @SuppressWarnings("UnusedDeclaration")
+ public class WebEventHandler implements EventHandler {
+
+ @Override
+ public void handleEvent(Event event) {
+ countDownLatch.countDown();
+ }
+
+ public WebEventHandler(String handlerName) {
+ this.handlerName = handlerName;
+ }
+ private final String handlerName;
+
+ protected String getHandlerName() {
+ return handlerName;
+ }
+ }
+
+ /**
+ * Configuration method called by pax-exam framework
+ *
+ * @return
+ */
+ @SuppressWarnings("UnusedDeclaration")
+ @Configuration
+ public Option[] configuration() {
+ List<Option> options = new LinkedList<Option>();
+
+ options.addAll(genericOsgiOptions());
+ options.addAll(jettyOptions());
+ options.addAll(osgiRuntimeOptions());
+
+ return options.toArray(new Option[options.size()]);
+ }
+
+ /**
+ * Registers the event handler for custom jersey/test/DEPLOYED event
+ */
+ public void defaultMandatoryBeforeMethod() throws Exception {
+ bundleContext.registerService(EventHandler.class.getName(),
+ new WebEventHandler("Deploy Handler"), getHandlerServiceProperties("jersey/test/DEPLOYED"));
+
+ assertNotNull(System.getSecurityManager());
+
+ updatePermissionsFromFile();
+ }
+
+ /**
+ * The test method itself - installs the war-bundle and sends two testing
+ * requests
+ *
+ * @throws Exception
+ */
+ public WebTarget webAppTestTarget(String appRoot) throws Exception {
+
+ LOGGER.info(bundleList());
+
+ // restart war bundle...
+ final Bundle warBundle = lookupWarBundle();
+ warBundle.stop();
+ warBundle.start();
+
+ // and wait until it's ready
+ LOGGER.fine("Waiting for jersey/test/DEPLOYED event with timeout " + MAX_WAITING_SECONDS + " seconds...");
+ if (!countDownLatch.await(MAX_WAITING_SECONDS, TimeUnit.SECONDS)) {
+ throw new TimeoutException("The event jersey/test/DEPLOYED did not arrive in "
+ + MAX_WAITING_SECONDS
+ + " seconds. Waiting timed out.");
+ }
+ final Client c = ClientBuilder.newClient();
+
+ // server should be listening now and everything should be initialized
+ return c.target(baseUri + appRoot);
+
+ }
+
+ private Bundle lookupWarBundle() {
+ for (Bundle b : bundleContext.getBundles()) {
+ if (b.getSymbolicName().contains("war-bundle")) {
+ return b;
+ }
+ }
+ return null;
+ }
+
+ private static int getProperty(final String varName, int defaultValue) {
+ if (null == varName) {
+ return defaultValue;
+ }
+ String varValue = AccessController.doPrivileged(PropertiesHelper.getSystemProperty(varName));
+ if (null != varValue) {
+ try {
+ return Integer.parseInt(varValue);
+ } catch (NumberFormatException e) {
+ // will return default value below
+ }
+ }
+ return defaultValue;
+ }
+
+ @SuppressWarnings({"UseOfObsoleteCollectionType", "unchecked"})
+ private Dictionary getHandlerServiceProperties(String... topics) {
+ Dictionary result = new Hashtable();
+ result.put(EventConstants.EVENT_TOPIC, topics);
+ return result;
+ }
+
+ private String bundleList() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("-- Bundle list -- \n");
+ for (Bundle b : bundleContext.getBundles()) {
+ sb.append(String.format("%1$5s", "[" + b.getBundleId() + "]")).append(" ")
+ .append(String.format("%1$-70s", b.getSymbolicName())).append(" | ")
+ .append(String.format("%1$-20s", b.getVersion())).append(" |");
+ sb.append(stateString(b)).append(" |");
+ sb.append(b.getLocation()).append("\n");
+ }
+ sb.append("-- \n\n");
+ return sb.toString();
+ }
+
+ private String stateString(Bundle b) {
+ switch (b.getState()) {
+ case Bundle.ACTIVE:
+ return "ACTIVE";
+ case Bundle.INSTALLED:
+ return "INSTALLED";
+ case Bundle.RESOLVED:
+ return "RESOLVED";
+ default:
+ return "???";
+ }
+ }
+}
diff --git a/examples/osgi-helloworld-webapp/functional-test/src/test/java/org/glassfish/jersey/examples/helloworld/test/WebAppFelixTest.java b/examples/osgi-helloworld-webapp/functional-test/src/test/java/org/glassfish/jersey/examples/helloworld/test/WebAppFelixTest.java
new file mode 100644
index 0000000..4a6c251
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/functional-test/src/test/java/org/glassfish/jersey/examples/helloworld/test/WebAppFelixTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.helloworld.test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerSuite;
+import static org.junit.Assert.assertEquals;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerSuite.class)
+public class WebAppFelixTest extends AbstractWebAppTest {
+
+ private static final Logger LOGGER = Logger.getLogger(WebAppFelixTest.class.getName());
+
+ @Override
+ public List<Option> osgiRuntimeOptions() {
+ return Arrays.asList(CoreOptions.options(
+ mavenBundle()
+ .groupId("org.apache.felix").artifactId("org.apache.felix.eventadmin")
+ .versionAsInProject()
+ )
+ );
+ }
+
+ @Before
+ public void before() throws Exception {
+ defaultMandatoryBeforeMethod();
+ }
+
+ @Test
+ public void testWebResources() throws Exception {
+ final WebTarget target = webAppTestTarget("/webresources");
+
+ // send request and check response - helloworld resource
+ final String helloResult = target.path("/helloworld").request().build("GET").invoke().readEntity(
+ String.class);
+ LOGGER.info("HELLO RESULT = " + helloResult);
+ assertEquals("Hello World", helloResult);
+
+ // send request and check response - another resource
+ final String anotherResult = target.path("/another").request().build("GET").invoke()
+ .readEntity(String.class);
+
+ LOGGER.info("ANOTHER RESULT = " + anotherResult);
+ assertEquals("Another", anotherResult);
+
+ // send request and check response for the additional bundle - should fail now
+ final String additionalResult = target.path("/additional").request().build("GET").invoke()
+ .readEntity(String.class);
+
+ LOGGER.info("ADDITIONAL RESULT = " + additionalResult);
+ assertEquals("Additional Bundle!", additionalResult);
+
+ // send request and check response for the sub-packaged additional bundle
+ final String subAdditionalResult = target.path("/subadditional").request().build("GET").invoke()
+ .readEntity(String.class);
+
+ LOGGER.info("SUB-PACKAGED ADDITIONAL RESULT = " + subAdditionalResult);
+ assertEquals("Sub-packaged Additional Bundle!", subAdditionalResult);
+
+ // send request and check response for the WEB-INF classes located resource
+ final String webInfClassesResourceResult = target.path("/webinf").request().build("GET").invoke()
+ .readEntity(String.class);
+
+ LOGGER.info("WEB-INF CLASSES RESOURCE RESULT = " + webInfClassesResourceResult);
+ assertEquals("WebInfClassesResource", webInfClassesResourceResult);
+
+ // send request and check response for the WEB-INF classes located resource
+ final String webInfClassesSubPackagedResourceResult = target.path("/subwebinf").request().build("GET")
+ .invoke().readEntity(String.class);
+
+ LOGGER.info("WEB-INF CLASSES SUB-PACKAGED RESOURCE RESULT = " + webInfClassesSubPackagedResourceResult);
+ assertEquals("WebInfClassesSubPackagedResource", webInfClassesSubPackagedResourceResult);
+ }
+
+ @Test
+ public void testNonRecursiveWebResources() throws Exception {
+ final WebTarget target = webAppTestTarget("/n-webresources");
+
+ // send request and check response - helloworld resource
+ final String helloResult = target.path("/helloworld").request().build("GET").invoke().readEntity(
+ String.class);
+ LOGGER.info("HELLO RESULT = " + helloResult);
+ assertEquals("Hello World", helloResult);
+
+ // send request and check response - another resource
+ final String anotherResult = target.path("/another").request().build("GET").invoke()
+ .readEntity(String.class);
+
+ LOGGER.info("ANOTHER RESULT = " + anotherResult);
+ assertEquals("Another", anotherResult);
+
+ // send request and check response for the additional bundle - should fail now
+ final String additionalResult = target.path("/additional").request().build("GET").invoke()
+ .readEntity(String.class);
+
+ LOGGER.info("ADDITIONAL RESULT = " + additionalResult);
+ assertEquals("Additional Bundle!", additionalResult);
+
+ // send request and check response for the sub-packaged additional bundle
+ final Response subAdditionalResponse = target.path("/subadditional").request().build("GET").invoke();
+
+ LOGGER.info("SUB-PACKAGED ADDITIONAL http status = " + subAdditionalResponse.getStatus());
+ assertEquals(Response.Status.NOT_FOUND.getStatusCode(), subAdditionalResponse.getStatus());
+
+ // send request and check response for the WEB-INF classes located resource
+ final String webInfClassesResourceResult = target.path("/webinf").request().build("GET").invoke()
+ .readEntity(String.class);
+
+ LOGGER.info("WEB-INF CLASSES RESOURCE RESULT = " + webInfClassesResourceResult);
+ assertEquals("WebInfClassesResource", webInfClassesResourceResult);
+
+ // send request and check response for the WEB-INF classes located resource
+ final Response webInfClassesSubPackagedResourceResponse = target.path("/subwebinf").request().build("GET")
+ .invoke();
+
+ LOGGER.info("WEB-INF CLASSES SUB-PACKAGED http status = " + webInfClassesSubPackagedResourceResponse.getStatus());
+ assertEquals(Response.Status.NOT_FOUND.getStatusCode(), webInfClassesSubPackagedResourceResponse.getStatus());
+ }
+}
diff --git a/examples/osgi-helloworld-webapp/functional-test/src/test/resources/felix.policy b/examples/osgi-helloworld-webapp/functional-test/src/test/resources/felix.policy
new file mode 100644
index 0000000..abe0d50
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/functional-test/src/test/resources/felix.policy
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*System*"] (java.security.AllPermission) } "allToSystem";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*System*"] (org.osgi.framework.AdminPermission) } "adminToSystem";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*System*"] (org.osgi.framework.PackagePermission) } "packageToSystem";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*additional-bundle*"] (java.security.AllPermission) } "allToAdditional";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*javassist*"] (java.security.AllPermission) } "allToJavassist";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*javax.ws.rs*"] (java.security.AllPermission) } "allToJavaxWsRs";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jetty*"] (java.security.AllPermission) } "allToJetty";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*hk2*"] (java.security.AllPermission) } "allToHk2";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*osgi*"] (java.security.AllPermission) } "allToOsgi";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*org.apache*"] (java.security.AllPermission) } "allToApache";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*org.apache*"] (org.osgi.framework.AdminPermission) } "adminToApache";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*org.apache*"] (org.osgi.framework.PackagePermission) } "PackageToApache";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*org.ops4j*"] (java.security.AllPermission) } "allToOps4J";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*org.ops4j*"] (org.osgi.framework.AdminPermission) } "adminToOps4J";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*org.ops4j*"] (org.osgi.framework.PackagePermission) } "packageToOps4J";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*client-bundle*"] (java.security.AllPermission) } "allToCB";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*war-bundle*"] (java.security.AllPermission) } "allToWar";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*war-bundle*"] (org.osgi.framework.AdminPermission) } "adminToWar";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "local"] (java.security.AllPermission) } "allToLocal";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "local"] (org.osgi.framework.AdminPermission) } "adminToLocal";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "local"] (org.osgi.framework.PackagePermission) } "packageToLocal";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-common*"] (org.osgi.framework.AdminPermission "*" "class,resolve,resource") } "adminPermissionToJerseyCommon";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-common*"] (org.osgi.framework.PackagePermission) } "packagePermissionToJerseyCommon";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-common*"] (java.lang.reflect.ReflectPermission "suppressAccessChecks") } "suppressAccessChecksToJerseyCommon";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-common*"] (java.net.SocketPermission "*" "connect,resolve") } "socketPermissionToJerseyCommon";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-common*"] (java.lang.RuntimePermission "accessDeclaredMembers") } "accessDeclaredMembersToJerseyCommon";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-common*"] (java.lang.RuntimePermission "accessClassInPackage.sun.misc") } "accessClassInPackageSunMisc";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-common*"] (java.lang.RuntimePermission "getClassLoader") } "getCLToJerseyCommon";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-common*"] (java.lang.RuntimePermission "modifyThread") } "modifyThreadToJerseyCommon";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-common*"] (java.util.PropertyPermission "*" "read") } "propReadToJerseyCommon";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-common*"] (java.io.FilePermission "<<ALL FILES>>" "read,write") } "fileReadToJerseyCommon";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-client*"] (java.security.AllPermission) } "allToJerseyClient";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-server*"] (org.osgi.framework.AdminPermission "*" "class,resolve,resource") } "adminPermissionToJerseyServer";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-server*"] (java.lang.reflect.ReflectPermission "suppressAccessChecks") } "suppressAccessChecksToJerseyServer";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-server*"] (java.lang.RuntimePermission "accessDeclaredMembers") } "accessDeclaredMembersToJerseyServer";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-server*"] (java.lang.RuntimePermission "setContextClassLoader") } "setContextCLToJerseyServer";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-server*"] (java.lang.RuntimePermission "getClassLoader") } "getClassLoaderToJerseyServer";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-server*"] (java.util.PropertyPermission "*" "read") } "propReadToJerseyServer";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-server*"] (java.io.FilePermission "<<ALL FILES>>" "read,write") } "fileReadToJerseyServer";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-server*"] (java.lang.RuntimePermission "modifyThread") } "modifyThreadToJerseyServer";
+allow { [org.osgi.service.condpermadmin.BundleLocationCondition "*jersey-container*"] (java.security.AllPermission) } "allToJerseyContainer";
diff --git a/examples/osgi-helloworld-webapp/functional-test/src/test/resources/log4j.properties b/examples/osgi-helloworld-webapp/functional-test/src/test/resources/log4j.properties
new file mode 100644
index 0000000..4fd23df
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/functional-test/src/test/resources/log4j.properties
@@ -0,0 +1,14 @@
+#
+# Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Distribution License v. 1.0, which is available at
+# http://www.eclipse.org/org/documents/edl-v10.php.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+log4j.rootCategory=ERROR, stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout.ConversionPattern=[%30.30c{1}] - %m%n
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
diff --git a/examples/osgi-helloworld-webapp/functional-test/src/test/resources/runtime.policy b/examples/osgi-helloworld-webapp/functional-test/src/test/resources/runtime.policy
new file mode 100644
index 0000000..dcd1277
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/functional-test/src/test/resources/runtime.policy
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+grant {
+ permission java.security.AllPermission;
+};
diff --git a/examples/osgi-helloworld-webapp/lib-bundle/pom.xml b/examples/osgi-helloworld-webapp/lib-bundle/pom.xml
new file mode 100644
index 0000000..18586bf
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/lib-bundle/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>osgi-helloworld-webapp</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.examples.osgi-helloworld-webapp</groupId>
+ <artifactId>lib-bundle</artifactId>
+ <name>jersey-examples-osgi-helloworld-webapp-lib</name>
+
+ <description>OSGi Helloworld Webapp - lib bundle</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- to generate the MANIFEST-FILE required by the bundle -->
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <manifestLocation>${project.build.directory}/META-INF</manifestLocation>
+ <supportedProjectTypes>
+ <supportedProjectType>bundle</supportedProjectType>
+ <supportedProjectType>jar</supportedProjectType>
+ </supportedProjectTypes>
+ <instructions>
+ <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+ <Import-Package>*</Import-Package>
+ <Export-Package>org.glassfish.jersey.examples.osgi.helloworld.resource</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <!-- do not create source zip bundles -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <skipAssembly>true</skipAssembly>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/osgi-helloworld-webapp/lib-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/AnotherResource.java b/examples/osgi-helloworld-webapp/lib-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/AnotherResource.java
new file mode 100644
index 0000000..7d050e2
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/lib-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/AnotherResource.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgi.helloworld.resource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+@Path("/another")
+public class AnotherResource {
+
+ @GET
+ @Produces("text/plain")
+ public String getAnotherMessage() {
+ return "Another";
+ }
+}
diff --git a/examples/osgi-helloworld-webapp/lib-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/HelloWorldResource.java b/examples/osgi-helloworld-webapp/lib-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/HelloWorldResource.java
new file mode 100644
index 0000000..3998982
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/lib-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/HelloWorldResource.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgi.helloworld.resource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+@Path("/helloworld")
+public class HelloWorldResource {
+
+ @GET
+ @Produces("text/plain")
+ public String getClichedMessage() {
+ return "Hello World";
+ }
+}
diff --git a/examples/osgi-helloworld-webapp/lib-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/MyApplication.java b/examples/osgi-helloworld-webapp/lib-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/MyApplication.java
new file mode 100644
index 0000000..d779561
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/lib-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/MyApplication.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgi.helloworld.resource;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.core.Application;
+
+public class MyApplication extends Application {
+
+ static final Set<Class<?>> classes = new HashSet<Class<?>>() {
+ {
+ add(HelloWorldResource.class);
+ add(AnotherResource.class);
+ }};
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ return classes;
+ }
+}
diff --git a/examples/osgi-helloworld-webapp/pom.xml b/examples/osgi-helloworld-webapp/pom.xml
new file mode 100644
index 0000000..1461b5f
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/pom.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>osgi-helloworld-webapp</artifactId>
+ <name>jersey-examples-osgi-helloworld-webapp</name>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>war-bundle</module>
+ <module>functional-test</module>
+ <module>lib-bundle</module>
+ <module>additional-bundle</module>
+ <module>alternate-version-bundle</module>
+ </modules>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/osgi-helloworld-webapp/war-bundle/pom.xml b/examples/osgi-helloworld-webapp/war-bundle/pom.xml
new file mode 100644
index 0000000..5bf8b16
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/war-bundle/pom.xml
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>osgi-helloworld-webapp</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.examples.osgi-helloworld-webapp</groupId>
+ <artifactId>war-bundle</artifactId>
+ <name>jersey-examples-osgi-helloworld-webapp-wab</name>
+ <packaging>war</packaging>
+
+ <description>OSGi Helloworld Webapp WAR bundle</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.examples.osgi-helloworld-webapp</groupId>
+ <artifactId>lib-bundle</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.examples.osgi-helloworld-webapp</groupId>
+ <artifactId>additional-bundle</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.examples.osgi-helloworld-webapp</groupId>
+ <artifactId>alternate-version-bundle</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.5</version><!-- the current project's ${servlet2.version} is 2.4 and that's not enough -->
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.eventadmin</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- to generate the MANIFEST-FILE required by the bundle -->
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <manifestLocation>${project.build.directory}/META-INF</manifestLocation>
+ <supportedProjectTypes>
+ <supportedProjectType>bundle</supportedProjectType>
+ <supportedProjectType>war</supportedProjectType>
+ </supportedProjectTypes>
+ <instructions>
+ <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+ <Bundle-ClassPath>.,WEB-INF/classes</Bundle-ClassPath>
+ <Import-Package>
+ org.glassfish.jersey.servlet,
+ <!--
+ This package is exported by two different bundle versions of 'additional-bundle'
+ - one is in the maven module 'additional-bundle' with the current ${project.version},
+ - the other one is in the maven module 'alternate-version-bundle' with Bundle-Version 2.2.1
+ (the names of maven artifacts have to be unique, but the name of bundles
+ (defined in the maven-bundle-plugin configuration) is the same for both.
+
+ Both bundles (additional-bundle 2.2.1 and additional-bundle ${project.version} are loaded into
+ OSGi runtime, so that there are two versions of the same resource available, each one returning
+ different string as a response. If the import version is changed to [2.2,2.3) as prepared below,
+ the test will fail (the response returned from the alternate version of the bundle is different).
+ NOTE - to test this, the version has to be restricted from above, otherwise OSGi can choose any of
+ the two bundles, as version="2.2" actually means "version 2.2. or higher".
+ -->
+ <!-- TODO: newer bundle plugin (3.2.0) generates version string from SNAPSHOTS differently than 2.x version -->
+ org.glassfish.jersey.examples.osgi.helloworld.additional.resource,
+ <!-- org.glassfish.jersey.examples.osgi.helloworld.additional.resource;version="${project.version}",-->
+ <!-- org.glassfish.jersey.examples.osgi.helloworld.additional.resource;version="[2.2,2.3)", -->
+ *
+ </Import-Package>
+ <Export-Package>
+ org.glassfish.jersey.examples.osgi.helloworld,
+ org.glassfish.jersey.examples.osgi.helloworld.resource,
+ org.glassfish.jersey.examples.osgi.helloworld.additional.resource
+ </Export-Package>
+ <Embed-Directory>WEB-INF/lib</Embed-Directory>
+ <Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
+ <Embed-Transitive>true</Embed-Transitive>
+ <Webapp-Context>helloworld</Webapp-Context>
+ <Web-ContextPath>helloworld</Web-ContextPath>
+ <Bundle-Activator>org.glassfish.jersey.examples.osgi.helloworld.WebAppContextListener</Bundle-Activator>
+ <_wab>src/main/webapp</_wab>
+ </instructions>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-war-plugin</artifactId>
+ <configuration>
+ <attachClasses>true</attachClasses>
+ <archive>
+ <manifestFile>${project.build.directory}/META-INF/MANIFEST.MF</manifestFile>
+ <manifestEntries>
+ <Bundle-ClassPath>WEB-INF/classes</Bundle-ClassPath>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <!-- do not create source zip bundles -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <skipAssembly>true</skipAssembly>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>moxy</id>
+ <activation>
+ <property>
+ <name>moxy</name>
+ </property>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>org.eclipse.persistence.moxy</artifactId>
+ <version>${moxy.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/osgi-helloworld-webapp/war-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/WebAppContextListener.java b/examples/osgi-helloworld-webapp/war-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/WebAppContextListener.java
new file mode 100644
index 0000000..a7da75b
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/war-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/WebAppContextListener.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgi.helloworld;
+
+import java.util.HashMap;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+/**
+ * This is to make sure we signal the application has been deployed/un-deployed
+ * via the OSGi EventAdmin service.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class WebAppContextListener implements BundleActivator, ServletContextListener {
+
+ static EventAdmin ea;
+
+ BundleContext bc;
+ ServiceReference eaRef;
+
+ static synchronized EventAdmin getEa() {
+ return ea;
+ }
+
+ static synchronized void setEa(EventAdmin ea) {
+ WebAppContextListener.ea = ea;
+ }
+
+ @Override
+ public void contextInitialized(final ServletContextEvent sce) {
+ if (getEa() != null) {
+ final String contextPath = sce.getServletContext().getContextPath();
+ getEa().sendEvent(new Event("jersey/test/DEPLOYED", new HashMap<String, String>() {{
+ put("context-path", contextPath);
+ }}));
+ }
+ }
+
+ @Override
+ public void contextDestroyed(final ServletContextEvent sce) {
+ if (getEa() != null) {
+ getEa().sendEvent(new Event("jersey/test/UNDEPLOYED", new HashMap<String, String>() {{
+ put("context-path", sce.getServletContext().getContextPath());
+ }}));
+ }
+ }
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+ bc = context;
+ eaRef = bc.getServiceReference(EventAdmin.class.getName());
+ if (eaRef != null) {
+ setEa((EventAdmin) bc.getService(eaRef));
+ }
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ if (eaRef != null) {
+ setEa(null);
+ bc.ungetService(eaRef);
+ }
+ }
+}
diff --git a/examples/osgi-helloworld-webapp/war-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/WebInfClassesResource.java b/examples/osgi-helloworld-webapp/war-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/WebInfClassesResource.java
new file mode 100644
index 0000000..fd096c3
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/war-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/WebInfClassesResource.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgi.helloworld.resource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * This resource is physically located in WEB-INF/classes of a OSGI bundle.
+ *
+ * @author Stepan Vavra (stepan.vavra at oracle.com)
+ */
+@Path("/webinf")
+public class WebInfClassesResource {
+
+ @GET
+ @Produces("text/plain")
+ public String getWebInfMessage() {
+ return "WebInfClassesResource";
+ }
+
+}
diff --git a/examples/osgi-helloworld-webapp/war-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/subpackage/WebInfClassesSubPackagedResource.java b/examples/osgi-helloworld-webapp/war-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/subpackage/WebInfClassesSubPackagedResource.java
new file mode 100644
index 0000000..cf5a9a6
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/war-bundle/src/main/java/org/glassfish/jersey/examples/osgi/helloworld/resource/subpackage/WebInfClassesSubPackagedResource.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgi.helloworld.resource.subpackage;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * This resource is located in a sub-package and will be detected by OSGI framework only if recursive scanning is turned on.<br/>
+ * As a matter of fact, this resource is physically located in WEB-INF/classes which needs to be tested as well.
+ *
+ * @author Stepan Vavra (stepan.vavra at oracle.com)
+ */
+@Path("/subwebinf")
+public class WebInfClassesSubPackagedResource {
+
+ @GET
+ @Produces("text/plain")
+ public String getSubPackagedWebInfMessage() {
+ return "WebInfClassesSubPackagedResource";
+ }
+
+}
diff --git a/examples/osgi-helloworld-webapp/war-bundle/src/main/webapp/WEB-INF/web.xml b/examples/osgi-helloworld-webapp/war-bundle/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..3408caa
--- /dev/null
+++ b/examples/osgi-helloworld-webapp/war-bundle/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+ <listener>
+ <listener-class>org.glassfish.jersey.examples.osgi.helloworld.WebAppContextListener</listener-class>
+ </listener>
+ <!-- We do not really need the welcome files, but the jetty bundled with pax exam requires it -->
+ <welcome-file-list>
+ <welcome-file>index.jsp</welcome-file>
+ </welcome-file-list>
+
+ <!-- Jersey APP that has enabled recursive scanning (the default value) -->
+ <servlet>
+ <servlet-name>Jersey Web Application</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>jersey.config.server.provider.packages</param-name>
+ <param-value>
+ org.glassfish.jersey.examples.osgi.helloworld.resource,
+ org.glassfish.jersey.examples.osgi.helloworld.additional.resource,
+ </param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>Jersey Web Application</servlet-name>
+ <url-pattern>/webresources/*</url-pattern>
+ </servlet-mapping>
+
+ <!-- Jersey APP with disabled recursive scanning -->
+ <servlet>
+ <servlet-name>Jersey Web Non-recursive Application</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>jersey.config.server.provider.packages</param-name>
+ <param-value>
+ org.glassfish.jersey.examples.osgi.helloworld.resource,
+ org.glassfish.jersey.examples.osgi.helloworld.additional.resource,
+ <!-- 'package' package path prevents regression in matching packages implemented in OsgiRegistry as it matches
+ string 'org.glassfish.jersey.examples.osgi.helloworld.resource.sub[package]' -->
+ package
+ </param-value>
+ </init-param>
+ <init-param>
+ <param-name>jersey.config.server.provider.scanning.recursive</param-name>
+ <param-value>false</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>Jersey Web Non-recursive Application</servlet-name>
+ <url-pattern>/n-webresources/*</url-pattern>
+ </servlet-mapping>
+</web-app>
diff --git a/examples/osgi-http-service/README.MD b/examples/osgi-http-service/README.MD
new file mode 100644
index 0000000..0e09ad0
--- /dev/null
+++ b/examples/osgi-http-service/README.MD
@@ -0,0 +1,41 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+OSGi HttpService Example
+========================
+
+This example demonstrates how to develop a simple OSGi bundle
+registering a Jersey based RESTful web service as an [OSGi
+HttpService](http://www.osgi.org/javadoc/r4v42/org/osgi/service/http/HttpService.html)
+
+Contents
+--------
+
+The example bundle (see the `bundle` module) consists of just one Jersey resource:
+
+`org.glassfish.jersey.examples.osgihttpservice.StatusResource`
+
+that produces a textual response to an HTTP GET
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP method
+--------------- | ---------------- | -------------
+**_/status_** | StatusResource | GET
+
+Running the Example
+-------------------
+
+To run the example, you would need to build the OSGi bundle in the
+`bundle` module and install it to an OSGi runtime (e.g. Apache Felix)
+together with other OSGi bundles. Look at the attached `functional-test`
+module for details on the programatical runtime configuration
+
+To build the bundle jar file and run the tests, you can just launch
+
+> mvn clean install
diff --git a/examples/osgi-http-service/bundle/pom.xml b/examples/osgi-http-service/bundle/pom.xml
new file mode 100644
index 0000000..4ee914e
--- /dev/null
+++ b/examples/osgi-http-service/bundle/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>osgi-http-service</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.examples.osgi-http-service</groupId>
+ <artifactId>bundle</artifactId>
+ <name>jersey-examples-osgi-http-service-bundle</name>
+ <packaging>jar</packaging>
+
+ <description>OSGi HttpService example bundle</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.http.bundle</artifactId>
+ <version>2.2.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.eventadmin</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>org.glassfish.jersey.examples.osgihttpservice</Export-Package>
+ <Import-Package>javax.servlet.*;version="[2.4,5.0)",*</Import-Package>
+ <Bundle-Activator>org.glassfish.jersey.examples.osgihttpservice.Activator</Bundle-Activator>
+ <Implementation-Title>jersey-osgi-http-service-bundle</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ </instructions>
+ <unpackBundle>true</unpackBundle>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <!-- do not create source zip bundles -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <skipAssembly>true</skipAssembly>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/osgi-http-service/bundle/src/main/java/org/glassfish/jersey/examples/osgihttpservice/Activator.java b/examples/osgi-http-service/bundle/src/main/java/org/glassfish/jersey/examples/osgihttpservice/Activator.java
new file mode 100644
index 0000000..7e26f36
--- /dev/null
+++ b/examples/osgi-http-service/bundle/src/main/java/org/glassfish/jersey/examples/osgihttpservice/Activator.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgihttpservice;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.logging.Logger;
+
+import javax.servlet.ServletException;
+
+import org.glassfish.jersey.servlet.ServletContainer;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.http.NamespaceException;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class Activator implements BundleActivator {
+
+ private BundleContext bc;
+ private ServiceTracker tracker;
+ private HttpService httpService = null;
+ private static final Logger logger = Logger.getLogger(Activator.class.getName());
+
+ @Override
+ public synchronized void start(BundleContext bundleContext) throws Exception {
+ this.bc = bundleContext;
+
+ logger.info("STARTING HTTP SERVICE BUNDLE");
+
+ this.tracker = new ServiceTracker(this.bc, HttpService.class.getName(), null) {
+
+ @Override
+ public Object addingService(ServiceReference serviceRef) {
+ httpService = (HttpService) super.addingService(serviceRef);
+ registerServlets();
+ return httpService;
+ }
+
+ @Override
+ public void removedService(ServiceReference ref, Object service) {
+ if (httpService == service) {
+ unregisterServlets();
+ httpService = null;
+ }
+ super.removedService(ref, service);
+ }
+ };
+
+ this.tracker.open();
+
+ logger.info("HTTP SERVICE BUNDLE STARTED");
+ }
+
+ @Override
+ public synchronized void stop(BundleContext bundleContext) throws Exception {
+ this.tracker.close();
+ }
+
+ private void registerServlets() {
+ try {
+ rawRegisterServlets();
+ } catch (InterruptedException | NamespaceException | ServletException ie) {
+ throw new RuntimeException(ie);
+ }
+ }
+
+ private void rawRegisterServlets() throws ServletException, NamespaceException, InterruptedException {
+ logger.info("JERSEY BUNDLE: REGISTERING SERVLETS");
+ logger.info("JERSEY BUNDLE: HTTP SERVICE = " + httpService.toString());
+
+ // TODO - temporary workaround
+ // This is a workaround related to issue JERSEY-2093; grizzly (1.9.5) needs to have the correct context
+ // classloader set
+ ClassLoader myClassLoader = getClass().getClassLoader();
+ ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(myClassLoader);
+ httpService.registerServlet("/jersey-http-service", new ServletContainer(), getJerseyServletParams(), null);
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalContextClassLoader);
+ }
+ // END of workaround - after grizzly updated to the recent version, only the inner call from try block will remain:
+ // httpService.registerServlet("/jersey-http-service", new ServletContainer(), getJerseyServletParams(), null);
+
+ sendAdminEvent();
+ logger.info("JERSEY BUNDLE: SERVLETS REGISTERED");
+ }
+
+ private void sendAdminEvent() {
+ ServiceReference eaRef = bc.getServiceReference(EventAdmin.class.getName());
+ if (eaRef != null) {
+ EventAdmin ea = (EventAdmin) bc.getService(eaRef);
+ ea.sendEvent(new Event("jersey/test/DEPLOYED", new HashMap<String, String>() {
+ {
+ put("context-path", "/");
+ }
+ }));
+ bc.ungetService(eaRef);
+ }
+ }
+
+ private void unregisterServlets() {
+ if (this.httpService != null) {
+ logger.info("JERSEY BUNDLE: UNREGISTERING SERVLETS");
+ httpService.unregister("/jersey-http-service");
+ logger.info("JERSEY BUNDLE: SERVLETS UNREGISTERED");
+ }
+ }
+
+ @SuppressWarnings("UseOfObsoleteCollectionType")
+ private Dictionary<String, String> getJerseyServletParams() {
+ Dictionary<String, String> jerseyServletParams = new Hashtable<>();
+ jerseyServletParams.put("javax.ws.rs.Application", JerseyApplication.class.getName());
+ return jerseyServletParams;
+ }
+}
diff --git a/examples/osgi-http-service/bundle/src/main/java/org/glassfish/jersey/examples/osgihttpservice/JerseyApplication.java b/examples/osgi-http-service/bundle/src/main/java/org/glassfish/jersey/examples/osgihttpservice/JerseyApplication.java
new file mode 100644
index 0000000..1c8278c
--- /dev/null
+++ b/examples/osgi-http-service/bundle/src/main/java/org/glassfish/jersey/examples/osgihttpservice/JerseyApplication.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgihttpservice;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.core.Application;
+
+public class JerseyApplication extends Application {
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ Set<Class<?>> result = new HashSet<Class<?>>();
+ result.add(StatusResource.class);
+ return result;
+ }
+
+}
diff --git a/examples/osgi-http-service/bundle/src/main/java/org/glassfish/jersey/examples/osgihttpservice/StatusResource.java b/examples/osgi-http-service/bundle/src/main/java/org/glassfish/jersey/examples/osgihttpservice/StatusResource.java
new file mode 100644
index 0000000..4ea3361
--- /dev/null
+++ b/examples/osgi-http-service/bundle/src/main/java/org/glassfish/jersey/examples/osgihttpservice/StatusResource.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgihttpservice;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("status")
+public class StatusResource {
+
+ @GET
+ @Produces("text/plain")
+ public String getStatus() {
+ return "active";
+ }
+}
diff --git a/examples/osgi-http-service/functional-test/pom.xml b/examples/osgi-http-service/functional-test/pom.xml
new file mode 100644
index 0000000..7d147fc
--- /dev/null
+++ b/examples/osgi-http-service/functional-test/pom.xml
@@ -0,0 +1,229 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>osgi-http-service</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.examples.osgi-http-service</groupId>
+ <artifactId>functional-test</artifactId>
+ <packaging>pom</packaging>
+ <name>jersey-examples-osgi-http-service-test</name>
+
+ <dependencies>
+ <!-- Example bundle -->
+ <dependency>
+ <groupId>org.glassfish.jersey.examples.osgi-http-service</groupId>
+ <artifactId>bundle</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>aopalliance-repackaged</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.javassist</groupId>
+ <artifactId>javassist</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- OSGi runtime - Felix -->
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.eventadmin</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Pax-Exam and JUnit dependencies -->
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-junit4</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-container-forked</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-link-mvn</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.web</groupId>
+ <artifactId>pax-web-jetty-bundle</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.web</groupId>
+ <artifactId>pax-web-extender-war</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Grizzly dependencies-->
+ <dependency>
+ <groupId>com.sun.grizzly.osgi</groupId>
+ <artifactId>grizzly-httpservice-bundle</artifactId>
+ <scope>test</scope>
+ <version>1.9.45</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-servlet</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Logging dependencies-->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <version>1.6.4</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>maven-paxexam-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>generate-config</id>
+ <goals>
+ <goal>generate-depends-file</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>test-compile</id>
+ <phase>test-compile</phase>
+ <goals>
+ <goal>testCompile</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>process-test-resources</phase>
+ <goals>
+ <goal>testResources</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkMode>always</forkMode>
+ <enableAssertions>false</enableAssertions>
+ </configuration>
+ <executions>
+ <execution>
+ <id>test</id>
+ <phase>test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <!-- do not create source zip bundles -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <skipAssembly>true</skipAssembly>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>sonar</id>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <!-- disable JaCoCo listener because it's not working with <forkMode>always</fork> (<reuseForks>false</reuseForks> respectively)
+ https://jira.sonarsource.com/browse/SONARJAVA-728 (https://github.com/SonarSource/sonar-java/pull/324) -->
+ <properties combine.self="override" />
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/osgi-http-service/functional-test/src/test/java/org/glassfish/jersey/examples/osgihttpservice/test/AbstractHttpServiceTest.java b/examples/osgi-http-service/functional-test/src/test/java/org/glassfish/jersey/examples/osgihttpservice/test/AbstractHttpServiceTest.java
new file mode 100644
index 0000000..8ec4109
--- /dev/null
+++ b/examples/osgi-http-service/functional-test/src/test/java/org/glassfish/jersey/examples/osgihttpservice/test/AbstractHttpServiceTest.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgihttpservice.test;
+
+import java.net.URI;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.UriBuilder;
+
+import javax.inject.Inject;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+import static org.junit.Assert.assertEquals;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+
+/**
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public abstract class AbstractHttpServiceTest {
+
+ @Inject
+ BundleContext bundleContext;
+
+ /** maximum waiting time for runtime initialization and Jersey deployment */
+ public static final long MAX_WAITING_SECONDS = 10L;
+
+ /** Latch for blocking the testing thread until the runtime is ready and Jersey deployed */
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+
+ private static final int port = getProperty("jersey.config.test.container.port", 8080);
+ private static final String CONTEXT = "/jersey-http-service";
+ private static final URI baseUri = UriBuilder.fromUri("http://localhost").port(port).path(CONTEXT).build();
+ private static final String BundleLocationProperty = "jersey.bundle.location";
+
+ private static final Logger LOGGER = Logger.getLogger(AbstractHttpServiceTest.class.getName());
+
+ public abstract List<Option> httpServiceProviderOptions();
+
+ public abstract List<Option> osgiRuntimeOptions();
+
+ public List<Option> genericOsgiOptions() {
+ @SuppressWarnings("RedundantStringToString")
+ final String bundleLocation = mavenBundle()
+ .groupId("org.glassfish.jersey.examples.osgi-http-service")
+ .artifactId("bundle")
+ .versionAsInProject().getURL().toString();
+
+ List<Option> options = Arrays.asList(options(
+ systemProperty("org.osgi.service.http.port").value(String.valueOf(port)),
+ systemProperty(BundleLocationProperty).value(bundleLocation),
+ systemProperty("jersey.config.test.container.port").value(String.valueOf(port)),
+ systemProperty("org.osgi.framework.system.packages.extra").value("javax.annotation"),
+
+ // do not remove the following line
+ // systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("FINEST"),
+
+ // uncomment for logging (do not remove the following two lines)
+ // mavenBundle("org.ops4j.pax.logging", "pax-logging-api", "1.4"),
+ // mavenBundle("org.ops4j.pax.logging", "pax-logging-service", "1.4"),
+
+ // javax.annotation has to go first!
+ mavenBundle().groupId("javax.annotation").artifactId("javax.annotation-api").versionAsInProject(),
+
+ mavenBundle("org.ops4j.pax.url", "pax-url-mvn"),
+ junitBundles(),
+
+ // HK2
+ mavenBundle().groupId("org.glassfish.hk2").artifactId("hk2-api").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2").artifactId("osgi-resource-locator").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2").artifactId("hk2-locator").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2").artifactId("hk2-utils").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2.external").artifactId("javax.inject").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.hk2.external").artifactId("aopalliance-repackaged").versionAsInProject(),
+ mavenBundle().groupId("org.javassist").artifactId("javassist").versionAsInProject(),
+
+ // JAX-RS API
+ mavenBundle().groupId("javax.ws.rs").artifactId("javax.ws.rs-api").versionAsInProject(),
+
+ // validation
+ mavenBundle().groupId("javax.validation").artifactId("validation-api").versionAsInProject(),
+
+ // Jersey bundles
+ mavenBundle().groupId("org.glassfish.jersey.core").artifactId("jersey-common").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.jersey.core").artifactId("jersey-server").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.jersey.core").artifactId("jersey-client").versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.jersey.containers").artifactId("jersey-container-servlet-core")
+ .versionAsInProject(),
+ mavenBundle().groupId("org.glassfish.jersey.inject").artifactId("jersey-hk2").versionAsInProject()
+ ));
+
+ final String localRepository = AccessController.doPrivileged(PropertiesHelper.getSystemProperty("localRepository"));
+ if (localRepository != null) {
+ options = new ArrayList<>(options);
+ options.add(systemProperty("org.ops4j.pax.url.mvn.localRepository").value(localRepository));
+ }
+
+ return options;
+ }
+
+ public List<Option> grizzlyOptions() {
+ return Arrays.asList(options(
+ mavenBundle().groupId("com.sun.grizzly.osgi").artifactId("grizzly-httpservice-bundle").versionAsInProject()
+ ));
+ }
+
+ public List<Option> jettyOptions() {
+ return Arrays.asList(options(
+ mavenBundle().groupId("org.ops4j.pax.web").artifactId("pax-web-jetty-bundle").versionAsInProject(),
+ mavenBundle().groupId("org.ops4j.pax.web").artifactId("pax-web-extender-war").versionAsInProject()));
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public class WebEventHandler implements EventHandler {
+
+ @Override
+ public void handleEvent(Event event) {
+ countDownLatch.countDown();
+ }
+
+ public WebEventHandler(String handlerName) {
+ this.handlerName = handlerName;
+ }
+
+ private final String handlerName;
+
+ protected String getHandlerName() {
+ return handlerName;
+ }
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ @Configuration
+ public Option[] configuration() {
+ final List<Option> options = new LinkedList<>();
+
+ options.addAll(genericOsgiOptions());
+ options.addAll(httpServiceProviderOptions());
+ options.addAll(osgiRuntimeOptions());
+
+ return options.toArray(new Option[options.size()]);
+ }
+
+ public void defaultMandatoryBeforeMethod() {
+ LOGGER.fine("Registering event handler for jersey/test/DEPLOYED");
+ bundleContext.registerService(EventHandler.class.getName(), new WebEventHandler("Deploy Handler"),
+ getHandlerServiceProperties("jersey/test/DEPLOYED"));
+ }
+
+ public void defaultHttpServiceTestMethod() throws Exception {
+ // log the list of bundles and result of the attempt to start
+ StringBuilder sb = new StringBuilder();
+ sb.append("-- Bundle list -- \n");
+ for (Bundle b : bundleContext.getBundles()) {
+ sb.append(String.format("%1$5s", "[" + b.getBundleId() + "]")).append(" ")
+ .append(String.format("%1$-70s", b.getSymbolicName())).append(" | ")
+ .append(String.format("%1$-20s", b.getVersion())).append(" |");
+ try {
+ b.start();
+ sb.append(" STARTED | ");
+ } catch (BundleException e) {
+ sb.append(" *FAILED* | ").append(e.getMessage());
+ }
+ sb.append(b.getLocation()).append("\n");
+ }
+ sb.append("-- \n\n");
+ LOGGER.fine(sb.toString());
+
+ // start the example bundle
+ bundleContext.installBundle(
+ AccessController.doPrivileged(PropertiesHelper.getSystemProperty(BundleLocationProperty))
+ ).start();
+
+ LOGGER.fine("Waiting for jersey/test/DEPLOYED event with timeout " + MAX_WAITING_SECONDS + " seconds...");
+ if (!countDownLatch.await(MAX_WAITING_SECONDS, TimeUnit.SECONDS)) {
+ throw new TimeoutException("The event jersey/test/DEPLOYED did not arrive in "
+ + MAX_WAITING_SECONDS
+ + " seconds. Waiting timed out.");
+ }
+
+ Client c = ClientBuilder.newClient();
+ final WebTarget target = c.target(baseUri);
+
+ String result = target.path("/status").request().build("GET").invoke().readEntity(String.class);
+
+ LOGGER.info("JERSEY RESULT = " + result);
+ assertEquals("active", result);
+ }
+
+ public static int getProperty(final String varName, int defaultValue) {
+ if (null == varName) {
+ return defaultValue;
+ }
+ String varValue = AccessController.doPrivileged(PropertiesHelper.getSystemProperty(varName));
+ if (null != varValue) {
+ try {
+ return Integer.parseInt(varValue);
+ } catch (NumberFormatException e) {
+ // will return default value below
+ }
+ }
+ return defaultValue;
+ }
+
+ @SuppressWarnings({"UseOfObsoleteCollectionType", "unchecked"})
+ private Dictionary getHandlerServiceProperties(String... topics) {
+ Dictionary result = new Hashtable();
+ result.put(EventConstants.EVENT_TOPIC, topics);
+ return result;
+ }
+}
diff --git a/examples/osgi-http-service/functional-test/src/test/java/org/glassfish/jersey/examples/osgihttpservice/test/GrizzlyHttpServiceFelixTest.java b/examples/osgi-http-service/functional-test/src/test/java/org/glassfish/jersey/examples/osgihttpservice/test/GrizzlyHttpServiceFelixTest.java
new file mode 100644
index 0000000..7e77916
--- /dev/null
+++ b/examples/osgi-http-service/functional-test/src/test/java/org/glassfish/jersey/examples/osgihttpservice/test/GrizzlyHttpServiceFelixTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgihttpservice.test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+
+@RunWith(PaxExam.class)
+public class GrizzlyHttpServiceFelixTest extends AbstractHttpServiceTest {
+
+ @Override
+ public List<Option> osgiRuntimeOptions() {
+ return Arrays.asList(CoreOptions.options(
+ mavenBundle()
+ .groupId("org.apache.felix").artifactId("org.apache.felix.eventadmin")
+ .versionAsInProject()
+ )
+ );
+ }
+
+ @Override
+ public List<Option> httpServiceProviderOptions() {
+ return grizzlyOptions();
+ }
+
+ @Before
+ public void before() {
+ defaultMandatoryBeforeMethod();
+ }
+
+ @Test
+ public void testHttpServiceMethod() throws Exception {
+ defaultHttpServiceTestMethod();
+ }
+}
+
diff --git a/examples/osgi-http-service/functional-test/src/test/java/org/glassfish/jersey/examples/osgihttpservice/test/JettyHttpServiceFelixTest.java b/examples/osgi-http-service/functional-test/src/test/java/org/glassfish/jersey/examples/osgihttpservice/test/JettyHttpServiceFelixTest.java
new file mode 100644
index 0000000..f73bb11
--- /dev/null
+++ b/examples/osgi-http-service/functional-test/src/test/java/org/glassfish/jersey/examples/osgihttpservice/test/JettyHttpServiceFelixTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.osgihttpservice.test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+
+@RunWith(PaxExam.class)
+public class JettyHttpServiceFelixTest extends AbstractHttpServiceTest {
+
+ @Override
+ public List<Option> osgiRuntimeOptions() {
+ return Arrays.asList(CoreOptions.options(
+ mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.eventadmin").versionAsInProject()
+ ));
+ }
+
+ @Override
+ public List<Option> httpServiceProviderOptions() {
+ return jettyOptions();
+ }
+
+ @Before
+ public void before() {
+ defaultMandatoryBeforeMethod();
+ }
+
+ @Test
+ public void testHttpServiceMethod() throws Exception {
+ defaultHttpServiceTestMethod();
+ }
+}
+
diff --git a/examples/osgi-http-service/functional-test/src/test/resources/log4j.properties b/examples/osgi-http-service/functional-test/src/test/resources/log4j.properties
new file mode 100644
index 0000000..4fd23df
--- /dev/null
+++ b/examples/osgi-http-service/functional-test/src/test/resources/log4j.properties
@@ -0,0 +1,14 @@
+#
+# Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Distribution License v. 1.0, which is available at
+# http://www.eclipse.org/org/documents/edl-v10.php.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+log4j.rootCategory=ERROR, stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout.ConversionPattern=[%30.30c{1}] - %m%n
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
diff --git a/examples/osgi-http-service/pom.xml b/examples/osgi-http-service/pom.xml
new file mode 100644
index 0000000..26b2b0f
--- /dev/null
+++ b/examples/osgi-http-service/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>osgi-http-service</artifactId>
+ <name>jersey-examples-osgi-http-service</name>
+ <packaging>pom</packaging>
+
+ <description>OSGi HttpService example</description>
+
+ <modules>
+ <module>bundle</module>
+ <module>functional-test</module>
+ </modules>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/pom.xml b/examples/pom.xml
new file mode 100644
index 0000000..20d8a70
--- /dev/null
+++ b/examples/pom.xml
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <licenses>
+ <license>
+ <name>Eclipse Distribution License v. 1.0</name>
+ <url>http://www.eclipse.org/org/documents/edl-v10.php</url>
+ <distribution>repo</distribution>
+ <comments>Except for AngularJS, bootstrap.js, jQuery, jQuery Barcode plugin, KineticJS
+ See also https://github.com/eclipse-ee4j/jersey/blob/master/NOTICE.md</comments>
+ </license>
+ <license>
+ <name>jQuery license</name>
+ <url>jquery.org/license</url>
+ <distribution>repo</distribution>
+ <comments>jQuery v1.12.4</comments>
+ </license>
+ <license>
+ <name>MIT license</name>
+ <url>http://www.opensource.org/licenses/mit-license.php</url>
+ <distribution>repo</distribution>
+ <comments>AngularJS, Bootstrap v3.3.7 (http://getbootstrap.com),
+ jQuery Barcode plugin 0.3, KineticJS v4.7.1</comments>
+ </license>
+ </licenses>
+
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <packaging>pom</packaging>
+ <name>jersey-examples</name>
+
+ <description>Jersey examples</description>
+
+ <modules>
+ <module>assemblies</module>
+ <module>bean-validation-webapp</module>
+ <module>bookmark</module>
+ <module>bookmark-em</module>
+ <module>bookstore-webapp</module>
+ <module>cdi-webapp</module>
+ <module>clipboard</module>
+ <module>clipboard-programmatic</module>
+ <module>declarative-linking</module>
+ <module>entity-filtering</module>
+ <module>entity-filtering-selectable</module>
+ <module>entity-filtering-security</module>
+ <module>extended-wadl-webapp</module>
+ <module>exception-mapping</module>
+ <module>feed-combiner-java8-webapp</module>
+ <module>freemarker-webapp</module>
+ <module>flight-mgmt-webapp</module>
+ <module>groovy</module>
+ <module>helloworld</module>
+ <module>helloworld-benchmark</module>
+ <module>helloworld-cdi2-se</module>
+ <module>helloworld-netty</module>
+ <module>helloworld-programmatic</module>
+ <module>helloworld-pure-jax-rs</module>
+ <module>helloworld-webapp</module>
+ <module>helloworld-weld</module>
+ <module>helloworld-spring-webapp</module>
+ <module>helloworld-spring-annotations</module>
+ <module>http-patch</module>
+ <module>http-trace</module>
+ <module>https-clientserver-grizzly</module>
+ <module>https-server-glassfish</module>
+ <module>java8-webapp</module>
+ <module>jaxb</module>
+ <module>jaxrs-types-injection</module>
+ <module>jersey-ejb</module>
+ <module>json-binding-webapp</module>
+ <module>json-jackson</module>
+ <module>json-jackson1</module>
+ <module>json-jettison</module>
+ <module>json-moxy</module>
+ <module>json-processing-webapp</module>
+ <module>json-with-padding</module>
+ <module>managed-beans-webapp</module>
+ <module>managed-client</module>
+ <module>managed-client-webapp</module>
+ <module>managed-client-simple-webapp</module>
+ <module>monitoring-webapp</module>
+ <module>multipart-webapp</module>
+ <module>open-tracing</module>
+ <module>osgi-helloworld-webapp</module>
+ <module>osgi-http-service</module>
+ <module>oauth-client-twitter</module>
+ <module>oauth2-client-google-webapp</module>
+ <module>reload</module>
+ <module>rx-client-webapp</module>
+ <module>server-async</module>
+ <module>server-async-managed</module>
+ <module>server-async-standalone</module>
+ <module>server-sent-events-jersey</module>
+ <module>server-sent-events-jaxrs</module>
+ <module>servlet3-webapp</module>
+ <module>simple-console</module>
+ <module>shortener-webapp</module>
+ <module>sparklines</module>
+ <module>sse-item-store-jersey-webapp</module>
+ <module>sse-item-store-jaxrs-webapp</module>
+ <module>sse-twitter-aggregator</module>
+ <module>system-properties-example</module>
+ <module>tone-generator</module>
+ <module>webapp-example-parent</module>
+ <module>xml-moxy</module>
+ </modules>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey</groupId>
+ <artifactId>jersey-bom</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <finalName>${project.artifactId}</finalName>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <dependencies>
+ <!-- Contains shared Jersey example assembly descriptors-->
+ <dependency>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>assemblies</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+ <executions>
+ <execution>
+ <id>assemble-src-zip</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <!-- Reference to a descriptor in org.glassfish.jersey.examples:assemblies module -->
+ <descriptorRef>src-zip</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>java</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+</project>
diff --git a/examples/reload/README.MD b/examples/reload/README.MD
new file mode 100644
index 0000000..57a857b
--- /dev/null
+++ b/examples/reload/README.MD
@@ -0,0 +1,69 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Resource Configuration Reload Example
+=====================================
+
+This example demonstrates how to create a simple Jersey application with
+resource reloading capability using
+[Grizzly HTTP Server container](http://grizzly.java.net).
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | Content | HTTP methods
+--------------------------- | --------------------------------------------------------- | ------------------------------------------------ | --------------
+**_/flights/arrivals_** | org.glassfish.jersey.examples.reload.ArrivalsResource | Dummy Arrivals Information | GET
+**_/flights/departures_** | org.glassfish.jersey.examples.reload.DeparturesResource | Dummy Departures Information | GET
+**_/flights/stats_** | org.glassfish.jersey.examples.reload.StatsResource | Statistics on application resource utilization | GET
+
+The application gets configured via a plain text file named `resources`.
+The file contains a list of resource classes to be published by the
+application. After the application gets started, it watches the file system
+and reloads the application as file updates are being detected.
+Changes in JAX-RS resource source files and updates of the above mentioned resources file
+are being detected. Java code re-compilation is done as required.
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys current example using[Grizzly
+container](http://grizzly.java.net/). StatsResource should be available
+at <http://localhost:8080/flights/stats>
+
+You may want to check the other resources as well:
+
+- <http://localhost:8080/flights/departures>
+- <http://localhost:8080/flights/arrivals>
+
+Updated information on number of hits should be available at
+<http://localhost:8080/flights/stats>.
+Now you can try to edit the `resources` file and comment the stats
+resource out by using the `#` prefix. The point is you leave the
+application running while editing the file. The changes wil get picked
+up automatically. No need to manually restart the application. The
+`resource` file content should now look like follows:
+
+ org.glassfish.jersey.examples.reload.DeparturesResource
+ org.glassfish.jersey.examples.reload.ArrivalsResource
+ #org.glassfish.jersey.examples.reload.StatsResource
+
+Once you save the change, the stats resource should not be available,
+but the other, departures/arrivals, resources should remain functional.
+
+Now you can revert the change back and check the stats resource is back
+providing actual statistics information.
+
+You can also try to change the JAX-RS resources source code. These changes
+will be also picked up automatically.
diff --git a/examples/reload/pom.xml b/examples/reload/pom.xml
new file mode 100644
index 0000000..ebf097d
--- /dev/null
+++ b/examples/reload/pom.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>reload</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-reload</name>
+
+ <description>Jersey resource configuration reload example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <!--<version>2.2</version>-->
+ <executions>
+ <execution>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>build-classpath</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <outputFile>${project.build.directory}/classpath.properties</outputFile>
+ <outputFilterFile>true</outputFilterFile>
+ <includeScope>compile</includeScope>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>properties-maven-plugin</artifactId>
+ <version>1.0-alpha-2</version>
+ <executions>
+ <execution>
+ <id>read-project-properties</id>
+ <goals>
+ <goal>read-project-properties</goal>
+ </goals>
+ <!--<phase>initialize</phase>-->
+ <phase>generate-sources</phase>
+ <configuration>
+ <files>
+ <file>${project.build.directory}/classpath.properties</file>
+ </files>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.reload.App</mainClass>
+ <arguments><argument>-cp=${classpath}</argument></arguments>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/reload/resources b/examples/reload/resources
new file mode 100644
index 0000000..0ad113a
--- /dev/null
+++ b/examples/reload/resources
@@ -0,0 +1,3 @@
+org.glassfish.jersey.examples.reload.DeparturesResource
+org.glassfish.jersey.examples.reload.ArrivalsResource
+#org.glassfish.jersey.examples.reload.StatsResource
diff --git a/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/App.java b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/App.java
new file mode 100644
index 0000000..30244f6
--- /dev/null
+++ b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/App.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.reload;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardWatchEventKinds;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.examples.reload.compiler.AppClassLoader;
+import org.glassfish.jersey.examples.reload.compiler.Compiler;
+import org.glassfish.jersey.examples.reload.compiler.JavaFile;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerLifecycleListener;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+import com.sun.nio.file.SensitivityWatchEventModifier;
+
+/**
+ * Reload example application.
+ * <p/>
+ * A {@link ContainerLifecycleListener container listener} gets registered
+ * with the application. Upon application startup notification, the listener schedules
+ * a new {@link TimerTask timer task} to check a text file called {@code resources}
+ * every 2 seconds. When the text file change is detected, the application gets reloaded with
+ * a new {@link ResourceConfig resource configuration} including all
+ * resource classes listed in that file.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class App {
+
+ private static final Logger LOGGER = Logger.getLogger(App.class.getName());
+ private static final URI BASE_URI = URI.create("http://localhost:8080/flights/");
+ public static final String ROOT_PATH = "arrivals";
+ public static final String CONFIG_FILENAME = "resources";
+ public static final String SRC_MAIN_JAVA = "src/main/java";
+
+ static Container container;
+
+ static class FileCheckTask extends TimerTask {
+
+ @Override
+ public void run() {
+
+ WatchService watcher;
+
+ try {
+ watcher = FileSystems.getDefault().newWatchService();
+
+ Path srcDir = Paths.get("src/main/java/org/glassfish/jersey/examples/reload");
+ registerWatcher(watcher, srcDir);
+
+ Path configFilePath = Paths.get(".");
+ registerWatcher(watcher, configFilePath);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new RuntimeException("Could not initialize watcher service!");
+ }
+
+ for (;;) {
+
+ try {
+ final WatchKey watchKey = watcher.take();
+
+ try {
+ for (WatchEvent<?> event : watchKey.pollEvents()) {
+ final WatchEvent.Kind<?> kind = event.kind();
+ if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
+ WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
+ Path modifiedFile = pathEvent.context();
+ System.out.printf("FILE MODIFIED: %s\n", modifiedFile);
+ }
+ }
+ } finally {
+ watchKey.reset(); // so that consecutive events could be processed
+ }
+
+ final File configFile = new File(CONFIG_FILENAME);
+ reloadApp(configFile);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ private void registerWatcher(WatchService watcher, Path directory) throws IOException {
+ directory.register(watcher,
+ new WatchEvent.Kind[]{
+ StandardWatchEventKinds.ENTRY_MODIFY
+ },
+ SensitivityWatchEventModifier.HIGH);
+ }
+
+ private void reloadApp(final File configFile) {
+ LOGGER.info("Reloading resource classes:");
+ final ResourceConfig rc = createResourceConfig(configFile);
+ App.container.reload(rc);
+ }
+
+ }
+
+ private static ResourceConfig createResourceConfig(File configFile) {
+ final ResourceConfig rc = new ResourceConfig();
+
+ try {
+ final AppClassLoader appClassLoader = new AppClassLoader(Thread.currentThread().getContextClassLoader());
+ final List<JavaFile> javaFiles = getJavaFiles(configFile);
+
+ Compiler.compile(appClassLoader, javaFiles);
+
+ for (JavaFile javaFile : javaFiles) {
+ try {
+ rc.registerClasses(appClassLoader.loadClass(javaFile.getClassName()));
+ } catch (final ClassNotFoundException ex) {
+ LOGGER.info(String.format(" ! class %s not found.\n", javaFile.getClassName()));
+ }
+ }
+ } catch (final Exception ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ return rc;
+ }
+
+ private static List<JavaFile> getJavaFiles(File configFile) throws Exception {
+
+ final List<JavaFile> javaFiles = new LinkedList<>();
+
+ try (BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(configFile), "UTF-8"))) {
+ while (r.ready()) {
+ final String className = r.readLine();
+ if (!className.startsWith("#")) {
+ javaFiles.add(new JavaFile(className, SRC_MAIN_JAVA));
+ LOGGER.info(String.format(" + included class %s.\n", className));
+ } else {
+ LOGGER.info(String.format(" - ignored class %s\n", className.substring(1)));
+ }
+ }
+ }
+ return javaFiles;
+ }
+
+ public static void main(final String[] args) throws Exception {
+ try {
+ LOGGER.info("Resource Config Reload Jersey Example App");
+
+ for (String s : args) {
+ if (s.startsWith("-cp=")) {
+ Compiler.classpath = s.substring(4);
+ }
+ }
+
+ final ResourceConfig resourceConfig = createResourceConfig(new File(CONFIG_FILENAME));
+ registerReloader(resourceConfig);
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, true);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(
+ String.format("Application started.\nTry out %s%s\nStop the application using CTRL+C", BASE_URI, ROOT_PATH));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ private static Class<?> loadClass(String className) throws Exception {
+ final JavaFile javaFile = new JavaFile(className, SRC_MAIN_JAVA);
+ return Compiler.compile(className, javaFile);
+ }
+
+ private static void registerReloader(ResourceConfig resourceConfig) {
+ resourceConfig.registerInstances(new ContainerLifecycleListener() {
+ @Override
+ public void onStartup(final Container container) {
+ App.container = container;
+ final Timer t = new Timer(true);
+ t.schedule(new FileCheckTask(), 0);
+ }
+
+ @Override
+ public void onReload(final Container container) {
+ System.out.println("Application has been reloaded!");
+ }
+
+ @Override
+ public void onShutdown(final Container container) {
+ // ignore
+ }
+ });
+ }
+}
diff --git a/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/ArrivalsResource.java b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/ArrivalsResource.java
new file mode 100644
index 0000000..b849f5a
--- /dev/null
+++ b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/ArrivalsResource.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.reload;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("arrivals")
+public class ArrivalsResource {
+
+ @GET
+ @Produces("text/plain")
+ public String getArrivals() {
+ FlightsDB.arrivalsReqCount.incrementAndGet();
+ return "No arrival scheduled in the following days";
+ }
+}
diff --git a/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/DeparturesResource.java b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/DeparturesResource.java
new file mode 100644
index 0000000..80f9098
--- /dev/null
+++ b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/DeparturesResource.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.reload;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("departures")
+public class DeparturesResource {
+
+ @GET
+ @Produces("text/plain")
+ public String getDepartures() {
+ FlightsDB.departuresReqCount.incrementAndGet();
+ return "No departure scheduled in the following days";
+ }
+}
diff --git a/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/FlightsDB.java b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/FlightsDB.java
new file mode 100644
index 0000000..5de93db
--- /dev/null
+++ b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/FlightsDB.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.reload;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class FlightsDB {
+
+ static AtomicInteger departuresReqCount = new AtomicInteger();
+ static AtomicInteger arrivalsReqCount = new AtomicInteger();
+}
diff --git a/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/StatsResource.java b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/StatsResource.java
new file mode 100644
index 0000000..b10fca6
--- /dev/null
+++ b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/StatsResource.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.reload;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Path("stats")
+@Produces("text/plain")
+public class StatsResource {
+
+ @GET
+ public String getStats() {
+ return String.format("Arrivals resource hits: %d\nDepartures resource hits: %d",
+ FlightsDB.arrivalsReqCount.get(), FlightsDB.departuresReqCount.get());
+ }
+}
diff --git a/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/AppClassLoader.java b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/AppClassLoader.java
new file mode 100644
index 0000000..f9a8828
--- /dev/null
+++ b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/AppClassLoader.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.reload.compiler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * In order to load re-compiled classes we need
+ * to have a separate class-loader for each reload.
+ *
+ * Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class AppClassLoader extends ClassLoader {
+
+ private final Map<String, ClassFile> classFiles = new HashMap<>();
+
+ public AppClassLoader(ClassLoader parent) {
+ super(parent);
+ }
+
+ public void setCode(ClassFile cc) {
+ classFiles.put(cc.getName(), cc);
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ ClassFile cc = classFiles.get(name);
+ if (cc == null) {
+ return super.findClass(name);
+ }
+ byte[] byteCode = cc.getByteCode();
+ return defineClass(name, byteCode, 0, byteCode.length);
+ }
+
+ @Override
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ // we are cheating here, the parent already has the class, but we prefer our bytecode to be used.
+ ClassFile cc = classFiles.get(name);
+ if (cc == null) {
+ return super.loadClass(name);
+ }
+ byte[] byteCode = cc.getByteCode();
+ return defineClass(name, byteCode, 0, byteCode.length);
+ }
+}
diff --git a/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/ClassFile.java b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/ClassFile.java
new file mode 100644
index 0000000..b4511cf
--- /dev/null
+++ b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/ClassFile.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.reload.compiler;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.tools.SimpleJavaFileObject;
+
+/**
+ * Class file representation.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class ClassFile extends SimpleJavaFileObject {
+
+ private final String className;
+
+ private final ByteArrayOutputStream byteCode = new ByteArrayOutputStream();
+
+ /**
+ * Creates a new class file holder.
+ *
+ * @param className class name.
+ * @throws URISyntaxException in case given class name could not be represented as an URI.
+ */
+ public ClassFile(String className) throws URISyntaxException {
+ super(new URI(className), Kind.CLASS);
+ this.className = className;
+ }
+
+ /**
+ * Getter for class name associated with this class file.
+ *
+ * @return class name.
+ */
+ public String getClassName() {
+ return className;
+ }
+
+ @Override
+ public OutputStream openOutputStream() throws IOException {
+ return byteCode;
+ }
+
+ /**
+ * Returns byte code representation of the class after the class has been compiled.
+ *
+ * @return compiled byte code of the class.
+ */
+ public byte[] getByteCode() {
+ return byteCode.toByteArray();
+ }
+}
diff --git a/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/Compiler.java b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/Compiler.java
new file mode 100644
index 0000000..33602e3
--- /dev/null
+++ b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/Compiler.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.reload.compiler;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.ToolProvider;
+
+/**
+ * Java compiler utility.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class Compiler {
+
+ private static JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
+
+ /**
+ * Compiler classpath.
+ */
+ public static String classpath;
+
+ /**
+ * Compiles a single class and loads the class using a new class loader.
+ *
+ * @param className class to compile.
+ * @param sourceCode source code of the class to compile.
+ * @return loaded class
+ * @throws Exception
+ */
+ public static Class<?> compile(String className, SimpleJavaFileObject sourceCode) throws Exception {
+ ClassFile classFile = new ClassFile(className);
+
+ Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(sourceCode);
+ AppClassLoader cl = new AppClassLoader(Thread.currentThread().getContextClassLoader());
+ FileManager fileManager = new FileManager(javac.getStandardFileManager(null, null, null), Arrays.asList(classFile), cl);
+ JavaCompiler.CompilationTask task = javac.getTask(null, fileManager, null, getClOptions(), null, compilationUnits);
+ task.call();
+ return cl.loadClass(className);
+ }
+
+
+ /**
+ * Compiles multiple source files at once.
+ *
+ * @param appClassLoader common class loader for the classes.
+ * @param javaFiles source files to compile.
+ * @throws Exception in case something goes wrong.
+ */
+ public static void compile(AppClassLoader appClassLoader, List<JavaFile> javaFiles) throws Exception {
+
+ List<ClassFile> classes = new LinkedList<>();
+
+ for (JavaFile javaFile : javaFiles) {
+ classes.add(new ClassFile(javaFile.getClassName()));
+ }
+ Iterable<? extends JavaFileObject> compilationUnits = javaFiles;
+
+ FileManager fileManager = new FileManager(javac.getStandardFileManager(null, null, null), classes, appClassLoader);
+ JavaCompiler.CompilationTask task = javac.getTask(null, fileManager, null, getClOptions(), null, compilationUnits);
+ task.call();
+ }
+
+ private static List<String> getClOptions() {
+ List<String> optionList = new ArrayList<>();
+ optionList.addAll(Arrays.asList("-classpath", classpath + File.pathSeparator + "target/classes"));
+ return optionList;
+ }
+}
diff --git a/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/FileManager.java b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/FileManager.java
new file mode 100644
index 0000000..1bc611e
--- /dev/null
+++ b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/FileManager.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.reload.compiler;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.tools.FileObject;
+import javax.tools.ForwardingJavaFileManager;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+
+/**
+ * File manager delegator to control our source and class files.
+ *
+ * @author Jakub Podlesak (jakub.podlesak @ oracle.com)
+ */
+public class FileManager extends ForwardingJavaFileManager<JavaFileManager> {
+
+ private final Map<String, ClassFile> classFiles;
+ private final AppClassLoader cl;
+
+ /**
+ * Creates a new instance of FileManager.
+ *
+ * @param fileManager delegate to this file manager
+ * @param cl
+ */
+ protected FileManager(JavaFileManager fileManager, List<ClassFile> classFiles, AppClassLoader cl) {
+ super(fileManager);
+ this.classFiles = new HashMap<>();
+ this.cl = cl;
+ for (ClassFile classFile : classFiles) {
+ this.classFiles.put(classFile.getClassName(), classFile);
+ }
+ }
+
+ @Override
+ public JavaFileObject getJavaFileForOutput(
+ JavaFileManager.Location location,
+ String className,
+ JavaFileObject.Kind kind,
+ FileObject sibling) throws IOException {
+
+ final ClassFile classFile = classFiles.get(className);
+ if (classFile != null) {
+ this.cl.setCode(classFile);
+ }
+
+ return classFile;
+ }
+
+ @Override
+ public ClassLoader getClassLoader(JavaFileManager.Location location) {
+ return cl;
+ }
+}
diff --git a/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/JavaFile.java b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/JavaFile.java
new file mode 100644
index 0000000..a7458ef
--- /dev/null
+++ b/examples/reload/src/main/java/org/glassfish/jersey/examples/reload/compiler/JavaFile.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.reload.compiler;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import javax.tools.SimpleJavaFileObject;
+
+/**
+ * Java source file representation.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class JavaFile extends SimpleJavaFileObject {
+
+ private final String className;
+ private final String path;
+
+ public JavaFile(String className, String path) throws Exception {
+ super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
+ this.className = className;
+ this.path = path;
+ }
+
+ /**
+ * Class name getter.
+ *
+ * @return class name.
+ */
+ public String getClassName() {
+ return className;
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+
+ String filePath = path + File.separator + className.replace('.', '/') + Kind.SOURCE.extension;
+ final byte[] bytes = Files.readAllBytes(Paths.get(filePath));
+
+ return new String(bytes);
+ }
+}
diff --git a/examples/reload/src/test/java/org/glassfish/jersey/examples/reload/ReloadTest.java b/examples/reload/src/test/java/org/glassfish/jersey/examples/reload/ReloadTest.java
new file mode 100644
index 0000000..a9fd079
--- /dev/null
+++ b/examples/reload/src/test/java/org/glassfish/jersey/examples/reload/ReloadTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.reload;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * This is to test the reload feature without updating the resources text file.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class ReloadTest extends JerseyTest {
+
+ private static Container container;
+
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+
+ final ResourceConfig result = new ResourceConfig(ArrivalsResource.class);
+
+ result.registerInstances(new AbstractContainerLifecycleListener() {
+ @Override
+ public void onStartup(Container container) {
+ ReloadTest.container = container;
+ }
+ });
+
+ return result;
+ }
+
+ @Test
+ public void testReload() {
+
+ // hit arrivals
+ Response response = target().path("arrivals").request(MediaType.TEXT_PLAIN).get();
+ assertEquals(200, response.getStatus());
+
+ // make sure stats resource is not found
+ response = target().path("stats").request(MediaType.TEXT_PLAIN).get();
+ assertEquals(404, response.getStatus());
+
+ // add stats resource
+ container.reload(new ResourceConfig(ArrivalsResource.class, StatsResource.class));
+
+ // check stats
+ response = target().path("stats").request(MediaType.TEXT_PLAIN).get();
+ assertEquals(200, response.getStatus());
+ assertTrue("1 expected as number of arrivals hits in stats", response.readEntity(String.class).contains("1"));
+
+ // another arrivals hit
+ response = target().path("arrivals").request(MediaType.TEXT_PLAIN).get();
+ assertEquals(200, response.getStatus());
+
+ // check updated stats
+ response = target().path("stats").request(MediaType.TEXT_PLAIN).get();
+ assertEquals(200, response.getStatus());
+ assertTrue("2 expected as number of arrivals hits in stats", response.readEntity(String.class).contains("2"));
+
+ // remove stats
+ container.reload(new ResourceConfig(ArrivalsResource.class));
+
+ // make sure stats resource is not found
+ response = target().path("stats").request(MediaType.TEXT_PLAIN).get();
+ assertEquals(404, response.getStatus());
+ }
+}
diff --git a/examples/rx-client-webapp/README.MD b/examples/rx-client-webapp/README.MD
new file mode 100644
index 0000000..56b53cb
--- /dev/null
+++ b/examples/rx-client-webapp/README.MD
@@ -0,0 +1,160 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Reactive Clients Jersey Reactive Client Extensions Example
+==========================================================
+
+This example demonstrates how data from multiple resources and in
+various formats (json, xml) can be combined into a form suitable to ones
+requirements. The example show and compares usage of standard JAX-RS
+sync client, JAX-RS async client, Jersey's Observable (RxJava) client
+extension, Jersey's CompletionStage (Java 8) client extension and
+Jersey's ListenableFuture (Guava) client extension.
+
+The application consists of two parts:
+
+- **"Remote"**, that can be considered to be deployed on a
+ remote machine. In fact each resource from
+ `org.glassfish.jersey.examples.rx.remote` package can be deployed on
+ a separate remote machine as every resource represents a service of
+ it's own.
+
+- **Note:** `DestinationResource` returns response entities as `JSON`
+ while `CalculationResource` and `ForecastResource` as `XML`.
+
+- **"Agent"**, which is a synchronization/orchestration layer. This
+ layer, also deployed as a server application, fetches data from all
+ needed resources (using JAX-RS Client API) and combine
+ them together. The combined result is then sent to the client that
+ invoked the original request on the orchestration layer.
+
+Every "agent" resource (package `org.glassfish.jersey.examples.rx.agent`)
+invokes the following requests to the "remote" resource:
+
+- Obtain **visited** destinations for a user. (User identification is
+ propagated via request header and basically everything about a user
+ maximally simplified since it's out of scope of this example)
+
+- Obtain **recommended** destinations for a user. Requests obtaining
+ **visited** and **recommended** destinations are independent and can
+ be run in parallel.
+
+- Obtain weather **forecasts** for recommended destinations. New
+ client request is invoked for every recommended destination.
+ Obtaining weather **forecasts** depend on actual
+ **recommended** destinations.
+
+- Obtain trip **calculations** (prices) for recommended destinations.
+ New client request is invoked for every recommended destination.
+ Obtaining trip **calculations** depend on actual
+ **recommended** destinations. **forecast** and **calculation**
+ requests are independent on each other and can be invoked
+ in parallel.
+
+Contents
+--------
+
+> Please note that the example is not fully implemented right now.
+>
+> Available "agent" resources are _/rx/agent/sync_, _/rx/agent/async_ and _/rx/agent/completion_.
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Allowed values
+-------------------------------------------------- | ------------------------------- | -------------- | ------------------------------------------------------------------------------
+**_/rx/agent/sync_** | SyncAgentResource | GET | returns JSON
+**_/rx/agent/async_** | AsyncAgentResource | GET | returns JSON
+**_/rx/agent/observable_** **(temporarily n/a)** | ObservableAgentResource | GET | returns JSON
+**_/rx/agent/completion_** | CompletionStageAgentResource | GET | returns JSON
+**_/rx/agent/listenable_** **(temporarily n/a)** | ListenableFutureAgentResource | GET | returns JSON
+**_/rx/remote/destination/visited_** | DestinationResource | GET | returns JSON
+**_/rx/remote/destination/recommended_** | DestinationResource | GET | returns JSON
+**_/rx/remote/forecast/{destination}_** | ForecastResource | GET | destination - name of a country; returns XML (random value)
+**_/rx/remote/calculation/from/{from}/to/{to}_** | CalculationResource | GET | from - name of a country, to - name of a country; returns XML (random value)
+
+Application is Servlet 3 based, web.xml-less. Everything needed (resources/providers) is registered in `RxApplication`.
+
+Sample Response
+---------------
+
+Agent responses look similar to the following one:
+
+```javascript
+{
+ "visited" : [ {
+ "destination" : "Antigua & Barbuda"
+ }, {
+ "destination" : "Guinea"
+ }, {
+ "destination" : "Malta"
+ }, {
+ "destination" : "Denmark"
+ }, {
+ "destination" : "Tajikistan"
+ } ],
+ "recommended" : [ {
+ "destination" : "Bolivia",
+ "forecast" : "Showers",
+ "price" : 1359
+ }, {
+ "destination" : "Yemen",
+ "forecast" : "Haze",
+ "price" : 8032
+ }, {
+ "destination" : "Dominican Republic",
+ "forecast" : "Cloudy",
+ "price" : 1141
+ }, {
+ "destination" : "Korea South",
+ "forecast" : "Mostly Sunny",
+ "price" : 9853
+ }, {
+ "destination" : "Saudi Arabia",
+ "forecast" : "Fog",
+ "price" : 9063
+ } ],
+ "processingTime" : 877
+}
+```
+
+As can be seen the response entity contains 3 main elements: **visited**
+(list of visited destinations), **recommended** (list of recommended
+destinations + weather forecast and price calculation) and
+**processingTime** (describing how long it took to obtain previous two
+elements).
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean package jetty:run
+
+This deploys current example using Jetty. You can access the application at:
+
+- <http://localhost:8080/rx/agent/sync>
+- <http://localhost:8080/rx/agent/async>
+- <http://localhost:8080/rx/agent/listenable> **(temporarily n/a)**
+- <http://localhost:8080/rx/agent/observable> **(temporarily n/a)**
+- <http://localhost:8080/rx/agent/completion>
+
+Resources
+---------
+
+This examples is using the following (3-rd party) libraries:
+
+**RxJava** by Netflix
+- [GitHub](https://github.com/ReactiveX/RxJava)
+- [GitHub.Wiki](https://github.com/ReactiveX/RxJava/wiki)
+- [JavaDoc](http://reactivex.io/RxJava/javadoc/)
+
+**Guava** by Google
+- [Homepage](https://code.google.com/p/guava-libraries/)
+- [ListenableFuture
+ Explained](https://code.google.com/p/guava-libraries/wiki/ListenableFutureExplained)
+- [JavaDoc](http://docs.guava-libraries.googlecode.com/git/javadoc/index.html?overview-summary.html)
diff --git a/examples/rx-client-webapp/pom.xml b/examples/rx-client-webapp/pom.xml
new file mode 100644
index 0000000..14ab43d
--- /dev/null
+++ b/examples/rx-client-webapp/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>rx-client-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-rx-client-webapp</name>
+
+ <description>Jersey Reactive Client WebApp Example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.rx</groupId>
+ <artifactId>jersey-rx-client-guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.rx</groupId>
+ <artifactId>jersey-rx-client-rxjava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.rx</groupId>
+ <artifactId>jersey-rx-client-rxjava2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jackson</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Run the application using "mvn jetty:run" -->
+ <plugin>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-maven-plugin</artifactId>
+ <configuration>
+ <scanIntervalSeconds>5</scanIntervalSeconds>
+ <stopPort>9999</stopPort>
+ <stopKey>STOP</stopKey>
+ <webApp>
+ <contextPath>/</contextPath>
+ <webInfIncludeJarPattern>.*/.*jersey-[^/]\.jar$</webInfIncludeJarPattern>
+ </webApp>
+ <systemProperties>
+ <systemProperty>
+ <name>jetty.port</name>
+ <value>${jersey.config.test.container.port}</value>
+ </systemProperty>
+ </systemProperties>
+ <war>${project.build.directory}/${project.build.finalName}.war</war>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <properties>
+ <jersey.config.test.container.port>8080</jersey.config.test.container.port>
+ </properties>
+</project>
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/Helper.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/Helper.java
new file mode 100644
index 0000000..9e0f8c5
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/Helper.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * @author Michal Gajdos
+ */
+public class Helper {
+
+ public static void sleep() {
+ sleep(500);
+ }
+
+ public static void sleep(final long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ // NOOP.
+ }
+ }
+
+ public static List<String> getCountries(final int limit) {
+ return getCountries(limit, Collections.<String>emptyList());
+ }
+
+ public static List<String> getCountries(final int limit, final List<String> exclusions) {
+ final List<String> diff = new ArrayList<>(COUNTRIES);
+ diff.removeAll(exclusions);
+
+ if (limit >= diff.size()) {
+ return new ArrayList<>(diff);
+ } else {
+ final List<String> countries = new ArrayList<>(limit);
+ final Random random = new Random();
+
+ for (int i = 0; i < limit; i++) {
+ countries.add(diff.remove(random.nextInt(diff.size())));
+ }
+
+ return countries;
+ }
+ }
+
+ public static String getForecast() {
+ return WEATHER_CONDITIONS.get(new Random().nextInt(WEATHER_CONDITIONS.size()));
+ }
+
+ private static final List<String> COUNTRIES = Arrays.asList("Afghanistan",
+ "Albania",
+ "Algeria",
+ "Andorra",
+ "Angola",
+ "Antigua & Barbuda",
+ "Argentina",
+ "Armenia",
+ "Australia",
+ "Austria",
+ "Azerbaijan",
+ "Bahamas",
+ "Bahrain",
+ "Bangladesh",
+ "Barbados",
+ "Belarus",
+ "Belgium",
+ "Belize",
+ "Benin",
+ "Bhutan",
+ "Bolivia",
+ "Bosnia & Herzegovina",
+ "Botswana",
+ "Brazil",
+ "Brunei",
+ "Bulgaria",
+ "Burkina Faso",
+ "Burundi",
+ "Cambodia",
+ "Cameroon",
+ "Canada",
+ "Cape Verde",
+ "Central African Republic",
+ "Chad",
+ "Chile",
+ "China",
+ "Colombia",
+ "Comoros",
+ "Congo",
+ "Congo Democratic Republic",
+ "Costa Rica",
+ "Cote d'Ivoire",
+ "Croatia",
+ "Cuba",
+ "Cyprus",
+ "Czech Republic",
+ "Denmark",
+ "Djibouti",
+ "Dominica",
+ "Dominican Republic",
+ "Ecuador",
+ "East Timor",
+ "Egypt",
+ "El Salvador",
+ "Equatorial Guinea",
+ "Eritrea",
+ "Estonia",
+ "Ethiopia",
+ "Fiji",
+ "Finland",
+ "France",
+ "Gabon",
+ "Gambia",
+ "Georgia",
+ "Germany",
+ "Ghana",
+ "Greece",
+ "Grenada",
+ "Guatemala",
+ "Guinea",
+ "Guinea-Bissau",
+ "Guyana",
+ "Haiti",
+ "Honduras",
+ "Hungary",
+ "Iceland",
+ "India",
+ "Indonesia",
+ "Iran",
+ "Iraq",
+ "Ireland",
+ "Israel",
+ "Italy",
+ "Jamaica",
+ "Japan",
+ "Jordan",
+ "Kazakhstan",
+ "Kenya",
+ "Kiribati",
+ "Korea North",
+ "Korea South",
+ "Kosovo",
+ "Kuwait",
+ "Kyrgyzstan",
+ "Laos",
+ "Latvia",
+ "Lebanon",
+ "Lesotho",
+ "Liberia",
+ "Libya",
+ "Liechtenstein",
+ "Lithuania",
+ "Luxembourg",
+ "Macedonia",
+ "Madagascar",
+ "Malawi",
+ "Malaysia",
+ "Maldives",
+ "Mali",
+ "Malta",
+ "Marshall Islands",
+ "Mauritania",
+ "Mauritius",
+ "Mexico",
+ "Micronesia",
+ "Moldova",
+ "Monaco",
+ "Mongolia",
+ "Montenegro",
+ "Morocco",
+ "Mozambique",
+ "Myanmar (Burma)",
+ "Namibia",
+ "Nauru",
+ "Nepal",
+ "The Netherlands",
+ "New Zealand",
+ "Nicaragua",
+ "Niger",
+ "Nigeria",
+ "Norway",
+ "Oman",
+ "Pakistan",
+ "Palau",
+ "Palestinian State",
+ "Panama",
+ "Papua New Guinea",
+ "Paraguay",
+ "Peru",
+ "The Philippines",
+ "Poland",
+ "Portugal",
+ "Qatar",
+ "Romania",
+ "Russia",
+ "Rwanda",
+ "St. Kitts & Nevis",
+ "St. Lucia",
+ "St. Vincent & The Grenadines",
+ "Samoa",
+ "San Marino",
+ "Sao Tome & Principe",
+ "Saudi Arabia",
+ "Senegal",
+ "Serbia",
+ "Seychelles",
+ "Sierra Leone",
+ "Singapore",
+ "Slovakia",
+ "Slovenia",
+ "Solomon Islands",
+ "Somalia",
+ "South Africa",
+ "South Sudan",
+ "Spain",
+ "Sri Lanka",
+ "Sudan",
+ "Suriname",
+ "Swaziland",
+ "Sweden",
+ "Switzerland",
+ "Syria",
+ "Taiwan",
+ "Tajikistan",
+ "Tanzania",
+ "Thailand",
+ "Togo",
+ "Tonga",
+ "Trinidad & Tobago",
+ "Tunisia",
+ "Turkey",
+ "Turkmenistan",
+ "Tuvalu",
+ "Uganda",
+ "Ukraine",
+ "United Arab Emirates",
+ "United Kingdom",
+ "United States of America",
+ "Uruguay",
+ "Uzbekistan",
+ "Vanuatu",
+ "Vatican City (Holy See)",
+ "Venezuela",
+ "Vietnam",
+ "Yemen",
+ "Zambia",
+ "Zimbabwe");
+
+ private static final List<String> WEATHER_CONDITIONS = Arrays.asList("Partly Sunny",
+ "Scattered Thunderstorms",
+ "Showers",
+ "Scattered Showers",
+ "Rain and Snow",
+ "Overcast",
+ "Light Snow",
+ "Freezing Drizzle",
+ "Chance of Rain",
+ "Sunny",
+ "Clear",
+ "Mostly Sunny",
+ "Partly Cloudy",
+ "Mostly Cloudy",
+ "Chance of Storm",
+ "Rain",
+ "Chance of Snow",
+ "Cloudy",
+ "Mist",
+ "Storm",
+ "Thunderstorm",
+ "Chance of TStorm",
+ "Sleet",
+ "Snow",
+ "Icy",
+ "Dust",
+ "Fog",
+ "Smoke",
+ "Haze",
+ "Flurries",
+ "Light Rain",
+ "Snow Showers",
+ "Hail");
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/RxApplication.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/RxApplication.java
new file mode 100644
index 0000000..d6e3011
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/RxApplication.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.ext.ContextResolver;
+
+import org.glassfish.jersey.examples.rx.agent.AsyncAgentResource;
+import org.glassfish.jersey.examples.rx.agent.CompletionStageAgentResource;
+import org.glassfish.jersey.examples.rx.agent.FlowableAgentResource;
+import org.glassfish.jersey.examples.rx.agent.ListenableFutureAgentResource;
+import org.glassfish.jersey.examples.rx.agent.ObservableAgentResource;
+import org.glassfish.jersey.examples.rx.agent.SyncAgentResource;
+import org.glassfish.jersey.examples.rx.remote.CalculationResource;
+import org.glassfish.jersey.examples.rx.remote.DestinationResource;
+import org.glassfish.jersey.examples.rx.remote.ForecastResource;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+/**
+ * @author Michal Gajdos
+ */
+@ApplicationPath("rx")
+public class RxApplication extends ResourceConfig {
+
+ public RxApplication() {
+ // Remote (Server) Resources.
+ register(DestinationResource.class);
+ register(CalculationResource.class);
+ register(ForecastResource.class);
+
+ // Agent (Client) Resources.
+ register(SyncAgentResource.class);
+ register(AsyncAgentResource.class);
+ register(ObservableAgentResource.class);
+ register(FlowableAgentResource.class);
+ register(ListenableFutureAgentResource.class);
+ register(CompletionStageAgentResource.class);
+
+ // Providers.
+ register(JacksonFeature.class);
+ register(ObjectMapperProvider.class);
+ }
+
+ public static class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
+
+ @Override
+ public ObjectMapper getContext(final Class<?> type) {
+ return new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
+ }
+ }
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/AsyncAgentResource.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/AsyncAgentResource.java
new file mode 100644
index 0000000..a7353a2
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/AsyncAgentResource.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.agent;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+
+import org.glassfish.jersey.examples.rx.domain.AgentResponse;
+import org.glassfish.jersey.examples.rx.domain.Calculation;
+import org.glassfish.jersey.examples.rx.domain.Destination;
+import org.glassfish.jersey.examples.rx.domain.Forecast;
+import org.glassfish.jersey.examples.rx.domain.Recommendation;
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+import org.glassfish.jersey.server.ManagedAsync;
+import org.glassfish.jersey.server.Uri;
+
+/**
+ * Obtain information about visited (destination) and recommended (destination, forecast, price) places for "Async" user. Uses
+ * standard JAX-RS async approach to obtain the data.
+ *
+ * @author Michal Gajdos
+ */
+@Path("agent/async")
+@Produces("application/json")
+public class AsyncAgentResource {
+
+ @Uri("remote/destination")
+ private WebTarget destination;
+
+ @Uri("remote/calculation/from/{from}/to/{to}")
+ private WebTarget calculation;
+
+ @Uri("remote/forecast/{destination}")
+ private WebTarget forecast;
+
+ private final ExecutorService executor;
+
+ public AsyncAgentResource() {
+ executor = new ScheduledThreadPoolExecutor(20, new ThreadFactoryBuilder()
+ .setNameFormat("jersey-rx-client-async-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+ }
+
+ @GET
+ @ManagedAsync
+ public void async(@Suspended final AsyncResponse async) {
+ final long time = System.nanoTime();
+
+ final AgentResponse response = new AgentResponse();
+
+ final CountDownLatch outerLatch = new CountDownLatch(2);
+ final Queue<String> errors = new ConcurrentLinkedQueue<>();
+
+ // Obtain visited destinations.
+ destination.path("visited").request()
+ // Identify the user.
+ .header("Rx-User", "Async")
+ // Async invoker.
+ .async()
+ // Return a list of destinations
+ .get(new InvocationCallback<List<Destination>>() {
+ @Override
+ public void completed(final List<Destination> destinations) {
+ response.setVisited(destinations);
+ outerLatch.countDown();
+ }
+
+ @Override
+ public void failed(final Throwable throwable) {
+ errors.offer("Visited: " + throwable.getMessage());
+ outerLatch.countDown();
+ }
+ });
+
+
+ // Obtain recommended destinations. (does not depend on visited ones)
+ destination.path("recommended").request()
+ // Identify the user.
+ .header("Rx-User", "Async")
+ // Async invoker.
+ .async()
+ // Return a list of destinations.
+ .get(new InvocationCallback<List<Destination>>() {
+ @Override
+ public void completed(final List<Destination> recommended) {
+ final CountDownLatch innerLatch = new CountDownLatch(recommended.size() * 2);
+
+ // Forecasts. (depend on recommended destinations)
+ final Map<String, Forecast> forecasts = Collections.synchronizedMap(new HashMap<>());
+ for (final Destination dest : recommended) {
+ forecast.resolveTemplate("destination", dest.getDestination()).request()
+ .async()
+ .get(new InvocationCallback<Forecast>() {
+ @Override
+ public void completed(final Forecast forecast) {
+ forecasts.put(dest.getDestination(), forecast);
+ innerLatch.countDown();
+ }
+
+ @Override
+ public void failed(final Throwable throwable) {
+ errors.offer("Forecast: " + throwable.getMessage());
+ innerLatch.countDown();
+ }
+ });
+ }
+
+ // Calculations. (depend on recommended destinations)
+ final List<Future<Calculation>> futures = recommended.stream()
+ .map(dest -> calculation.resolveTemplate("from", "Moon").resolveTemplate("to",
+ dest.getDestination()).request().async().get(Calculation.class))
+ .collect(Collectors.toList());
+
+ final Map<String, Calculation> calculations = new HashMap<>();
+ while (!futures.isEmpty()) {
+ final Iterator<Future<Calculation>> iterator = futures.iterator();
+
+ while (iterator.hasNext()) {
+ final Future<Calculation> f = iterator.next();
+ if (f.isDone()) {
+ try {
+ final Calculation calculation = f.get();
+ calculations.put(calculation.getTo(), calculation);
+
+ innerLatch.countDown();
+ } catch (final Throwable t) {
+ errors.offer("Calculation: " + t.getMessage());
+ innerLatch.countDown();
+ } finally {
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ // Have to wait here for dependent requests ...
+ try {
+ if (!innerLatch.await(10, TimeUnit.SECONDS)) {
+ errors.offer("Inner: Waiting for requests to complete has timed out.");
+ }
+ } catch (final InterruptedException e) {
+ errors.offer("Inner: Waiting for requests to complete has been interrupted.");
+ }
+
+ // Recommendations.
+ final List<Recommendation> recommendations = new ArrayList<>(recommended.size());
+ for (final Destination dest : recommended) {
+ final Forecast fore = forecasts.get(dest.getDestination());
+ final Calculation calc = calculations.get(dest.getDestination());
+
+ recommendations.add(new Recommendation(dest.getDestination(),
+ fore != null ? fore.getForecast() : "N/A", calc != null ? calc.getPrice() : -1));
+ }
+ response.setRecommended(recommendations);
+
+ outerLatch.countDown();
+ }
+
+ @Override
+ public void failed(final Throwable throwable) {
+ errors.offer("Recommended: " + throwable.getMessage());
+ outerLatch.countDown();
+ }
+ });
+
+ // ... and have to wait also here for independent requests.
+ try {
+ if (!outerLatch.await(10, TimeUnit.SECONDS)) {
+ errors.offer("Outer: Waiting for requests to complete has timed out.");
+ }
+ } catch (final InterruptedException e) {
+ errors.offer("Outer: Waiting for requests to complete has been interrupted.");
+ }
+
+ // Do something with errors.
+ // ...
+
+ response.setProcessingTime((System.nanoTime() - time) / 1000000);
+ async.resume(response);
+ }
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/CompletionStageAgentResource.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/CompletionStageAgentResource.java
new file mode 100644
index 0000000..fd9f410
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/CompletionStageAgentResource.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.agent;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.examples.rx.domain.AgentResponse;
+import org.glassfish.jersey.examples.rx.domain.Calculation;
+import org.glassfish.jersey.examples.rx.domain.Destination;
+import org.glassfish.jersey.examples.rx.domain.Forecast;
+import org.glassfish.jersey.examples.rx.domain.Recommendation;
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.server.Uri;
+
+/**
+ * Obtain information about visited (destination) and recommended (destination, forecast, price) places for
+ * "CompletionStage" user. Uses Java 8 CompletionStage and Jersey Client to obtain the data.
+ *
+ * @author Michal Gajdos
+ */
+@Path("agent/completion")
+@Produces("application/json")
+public class CompletionStageAgentResource {
+
+ @Uri("remote/destination")
+ private WebTarget destinationTarget;
+
+ @Uri("remote/calculation/from/{from}/to/{to}")
+ private WebTarget calculationTarget;
+
+ @Uri("remote/forecast/{destination}")
+ private WebTarget forecastTarget;
+
+ private final ExecutorService executor;
+
+ public CompletionStageAgentResource() {
+ executor = new ScheduledThreadPoolExecutor(20,
+ new ThreadFactoryBuilder().setNameFormat("jersey-rx-client-completion-%d").build());
+ }
+
+ @GET
+ public void completion(@Suspended final AsyncResponse async) {
+ final long time = System.nanoTime();
+
+ final Queue<String> errors = new ConcurrentLinkedQueue<>();
+
+ CompletableFuture.completedFuture(new AgentResponse())
+ .thenCombine(visited(destinationTarget, executor, errors), AgentResponse::visited)
+ .thenCombine(recommended(destinationTarget, executor, errors), AgentResponse::recommended)
+ .whenCompleteAsync((response, throwable) -> {
+ // Do something with errors.
+
+ response.setProcessingTime((System.nanoTime() - time) / 1000000);
+ async.resume(throwable == null ? response : throwable);
+ });
+ }
+
+ private CompletionStage<List<Destination>> visited(final WebTarget destinationTarget,
+ final ExecutorService executor,
+ final Queue<String> errors) {
+ return destinationTarget.path("visited").request()
+ // Identify the user.
+ .header("Rx-User", "CompletionStage")
+ // Reactive invoker.
+ .rx()
+ // Return a list of destinations.
+ .get(new GenericType<List<Destination>>() {})
+ .exceptionally(throwable -> {
+ errors.offer("Visited: " + throwable.getMessage());
+ return Collections.emptyList();
+ });
+ }
+
+ private CompletionStage<List<Recommendation>> recommended(final WebTarget destinationTarget,
+ final ExecutorService executor,
+ final Queue<String> errors) {
+ // Recommended places.
+ final CompletionStage<List<Destination>> recommended = destinationTarget.path("recommended")
+ .request()
+ // Identify the user.
+ .header("Rx-User", "CompletionStage")
+ // Reactive invoker.
+ .rx()
+ // Return a list of destinations.
+ .get(new GenericType<List<Destination>>() {})
+ .exceptionally(throwable -> {
+ errors.offer("Recommended: " + throwable.getMessage());
+ return Collections.emptyList();
+ });
+
+ return recommended.thenCompose(destinations -> {
+ final WebTarget finalForecast = forecastTarget;
+ final WebTarget finalCalculation = calculationTarget;
+
+ List<CompletionStage<Recommendation>> recommendations = destinations.stream().map(destination -> {
+ // For each destination, obtain a weather forecast ...
+ final CompletionStage<Forecast> forecast =
+ finalForecast.resolveTemplate("destination", destination.getDestination())
+ .request().rx().get(Forecast.class)
+ .exceptionally(throwable -> {
+ errors.offer("Forecast: " + throwable.getMessage());
+ return new Forecast(destination.getDestination(), "N/A");
+ });
+ // ... and a price calculation
+ final CompletionStage<Calculation> calculation = finalCalculation.resolveTemplate("from", "Moon")
+ .resolveTemplate("to", destination.getDestination())
+ .request().rx().get(Calculation.class)
+ .exceptionally(throwable -> {
+ errors.offer("Calculation: " + throwable.getMessage());
+ return new Calculation("Moon", destination.getDestination(), -1);
+ });
+
+ //noinspection unchecked
+ return CompletableFuture.completedFuture(new Recommendation(destination))
+ // Set forecast for recommended destination.
+ .thenCombine(forecast, Recommendation::forecast)
+ // Set calculation for recommended destination.
+ .thenCombine(calculation, Recommendation::calculation);
+ }).collect(Collectors.toList());
+
+ // Transform List<CompletionStage<Recommendation>> to CompletionStage<List<Recommendation>>
+ return sequence(recommendations);
+ });
+ }
+
+ private <T> CompletionStage<List<T>> sequence(final List<CompletionStage<T>> stages) {
+ //noinspection SuspiciousToArrayCall
+ final CompletableFuture<Void> done = CompletableFuture.allOf(stages.toArray(new CompletableFuture[stages.size()]));
+
+ return done.thenApply(v -> stages.stream()
+ .map(CompletionStage::toCompletableFuture)
+ .map(CompletableFuture::join)
+ .collect(Collectors.<T>toList())
+ );
+ }
+
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/FlowableAgentResource.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/FlowableAgentResource.java
new file mode 100644
index 0000000..d5ffaef
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/FlowableAgentResource.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.agent;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.GenericType;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.client.rx.rxjava2.RxFlowableInvoker;
+import org.glassfish.jersey.client.rx.rxjava2.RxFlowableInvokerProvider;
+import org.glassfish.jersey.examples.rx.domain.AgentResponse;
+import org.glassfish.jersey.examples.rx.domain.Calculation;
+import org.glassfish.jersey.examples.rx.domain.Destination;
+import org.glassfish.jersey.examples.rx.domain.Forecast;
+import org.glassfish.jersey.examples.rx.domain.Recommendation;
+import org.glassfish.jersey.server.Uri;
+
+import io.reactivex.Flowable;
+import io.reactivex.schedulers.Schedulers;
+
+/**
+ * Obtain information about visited (destination) and recommended (destination, forecast, price) places for "RxJava2"
+ * user. Uses RxJava2 Flowable and Jersey Client to obtain the data.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Singleton
+@Path("agent/flowable")
+@Produces("application/json")
+public class FlowableAgentResource {
+
+ @Uri("remote/destination")
+ private WebTarget destination;
+
+ @Uri("remote/calculation/from/{from}/to/{to}")
+ private WebTarget calculation;
+
+ @Uri("remote/forecast/{destination}")
+ private WebTarget forecast;
+
+ @GET
+ public void flowable(@Suspended final AsyncResponse async) {
+ final long time = System.nanoTime();
+ final Queue<String> errors = new ConcurrentLinkedQueue<>();
+
+ Flowable.just(new AgentResponse())
+ // Obtain visited destinations.
+ .zipWith(visited(errors), (agentResponse, visited) -> {
+ agentResponse.setVisited(visited);
+ return agentResponse;
+ })
+ // Obtain recommended destinations. (does not depend on visited ones)
+ .zipWith(
+ recommended(errors),
+ (agentResponse, recommendations) -> {
+ agentResponse.setRecommended(recommendations);
+ return agentResponse;
+ })
+ // Observe on another thread than the one processing visited or recommended destinations.
+ .observeOn(Schedulers.io())
+ // Subscribe.
+ .subscribe(agentResponse -> {
+ agentResponse.setProcessingTime((System.nanoTime() - time) / 1000000);
+ async.resume(agentResponse);
+
+ }, async::resume);
+ }
+
+ private Flowable<List<Destination>> visited(final Queue<String> errors) {
+ destination.register(RxFlowableInvokerProvider.class);
+
+ return destination.path("visited").request()
+ // Identify the user.
+ .header("Rx-User", "RxJava2")
+ // Reactive invoker.
+ .rx(RxFlowableInvoker.class)
+ // Return a list of destinations.
+ .get(new GenericType<List<Destination>>() {
+ })
+ // Handle Errors.
+ .onErrorReturn(throwable -> {
+ errors.offer("Visited: " + throwable.getMessage());
+ return Collections.emptyList();
+ });
+ }
+
+ private Flowable<List<Recommendation>> recommended(final Queue<String> errors) {
+ destination.register(RxFlowableInvokerProvider.class);
+
+ // Recommended places.
+ final Flowable<Destination> recommended = destination.path("recommended").request()
+ // Identify the user.
+ .header("Rx-User", "RxJava2")
+ // Reactive invoker.
+ .rx(RxFlowableInvoker.class)
+ // Return a list of destinations.
+ .get(new GenericType<List<Destination>>() {
+ })
+ // Handle Errors.
+ .onErrorReturn(throwable -> {
+ errors.offer("Recommended: " + throwable
+ .getMessage());
+ return Collections.emptyList();
+ })
+ // Emit destinations one-by-one.
+ .flatMap(Flowable::fromIterable)
+ // Remember emitted items for dependant requests.
+ .cache();
+
+ forecast.register(RxFlowableInvokerProvider.class);
+
+ // Forecasts. (depend on recommended destinations)
+ final Flowable<Forecast> forecasts = recommended.flatMap(destination ->
+ forecast
+ .resolveTemplate("destination", destination.getDestination())
+ .request().rx(RxFlowableInvoker.class).get(Forecast.class)
+ .onErrorReturn(throwable -> {
+ errors.offer("Forecast: " + throwable.getMessage());
+ return new Forecast(destination.getDestination(), "N/A");
+ }));
+
+ calculation.register(RxFlowableInvokerProvider.class);
+
+ // Calculations. (depend on recommended destinations)
+ final Flowable<Calculation> calculations = recommended.flatMap(destination -> {
+ return calculation.resolveTemplate("from", "Moon").resolveTemplate("to", destination.getDestination())
+ .request().rx(RxFlowableInvoker.class).get(Calculation.class)
+ .onErrorReturn(throwable -> {
+ errors.offer("Calculation: " + throwable.getMessage());
+ return new Calculation("Moon", destination.getDestination(), -1);
+ });
+ });
+
+ return Flowable.zip(recommended, forecasts, calculations, Recommendation::new).buffer(Integer.MAX_VALUE);
+ }
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/ListenableFutureAgentResource.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/ListenableFutureAgentResource.java
new file mode 100644
index 0000000..9dc3d5f
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/ListenableFutureAgentResource.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.agent;
+
+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.client.WebTarget;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.client.rx.guava.RxListenableFutureInvoker;
+import org.glassfish.jersey.client.rx.guava.RxListenableFutureInvokerProvider;
+import org.glassfish.jersey.examples.rx.domain.AgentResponse;
+import org.glassfish.jersey.examples.rx.domain.Calculation;
+import org.glassfish.jersey.examples.rx.domain.Destination;
+import org.glassfish.jersey.examples.rx.domain.Forecast;
+import org.glassfish.jersey.examples.rx.domain.Recommendation;
+import org.glassfish.jersey.server.ManagedAsync;
+import org.glassfish.jersey.server.Uri;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Obtain information about visited (destination) and recommended (destination, forecast, price) places for "Guava" user. Uses
+ * Guava ListenableFuture and Jersey Client to obtain the data.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Path("agent/listenable")
+@Produces("application/json")
+public class ListenableFutureAgentResource {
+
+ @Uri("remote/destination")
+ private WebTarget destination;
+
+ @Uri("remote/calculation/from/{from}/to/{to}")
+ private WebTarget calculation;
+
+ @Uri("remote/forecast/{destination}")
+ private WebTarget forecast;
+
+ @GET
+ @ManagedAsync
+ public void listenable(@Suspended final AsyncResponse async) {
+ final long time = System.nanoTime();
+ final AgentResponse response = new AgentResponse();
+
+ // Obtain and set visited and recommended places to create a response ...
+ final ListenableFuture<List<AgentResponse>> successful = Futures.successfulAsList(
+ Arrays.asList(visited(response), recommended(response)));
+
+ // ... and when we have them, return response to the client.
+ Futures.addCallback(successful, new FutureCallback<List<AgentResponse>>() {
+
+ @Override
+ public void onSuccess(final List<AgentResponse> result) {
+ response.setProcessingTime((System.nanoTime() - time) / 1000000);
+ async.resume(response);
+ }
+
+ @Override
+ public void onFailure(final Throwable t) {
+ async.resume(t);
+ }
+ });
+ }
+
+ private ListenableFuture<AgentResponse> visited(final AgentResponse response) {
+ destination.register(RxListenableFutureInvokerProvider.class);
+
+ // Get a list of visited destinations ...
+ final ListenableFuture<List<Destination>> visited = destination.path("visited").request()
+ // Identify the user.
+ .header("Rx-User", "Guava")
+ // Reactive invoker.
+ .rx(RxListenableFutureInvoker.class)
+ // Return a list of destinations.
+ .get(new GenericType<List<Destination>>() {
+ });
+
+ // ... and set them to the final response.
+ return Futures.transform(visited, (AsyncFunction<List<Destination>, AgentResponse>) destinations -> {
+ response.setVisited(destinations);
+
+ return Futures.immediateFuture(response);
+ });
+ }
+
+ private ListenableFuture<AgentResponse> recommended(final AgentResponse response) {
+ destination.register(RxListenableFutureInvokerProvider.class);
+
+ // Get a list of recommended destinations ...
+ final ListenableFuture<List<Destination>> destinations = destination.path("recommended")
+ .request()
+ // Identify the user.
+ .header("Rx-User", "Guava")
+ // Reactive invoker.
+ .rx(RxListenableFutureInvoker.class)
+ // Return a list of destinations.
+ .get(new GenericType<List<Destination>>() {
+ });
+
+ // ... transform them to Recommendation instances ...
+ final ListenableFuture<List<Recommendation>> recommendations = Futures.transform(
+ destinations,
+ (AsyncFunction<List<Destination>, List<Recommendation>>) destinationList -> {
+ // Create new array list to avoid multiple remote calls.
+ final List<Recommendation> recommendationList = Lists.newArrayList(Lists.transform(
+ destinationList,
+ destination -> new Recommendation(destination.getDestination(), null, 0)));
+
+ return Futures.immediateFuture(recommendationList);
+ });
+
+ // ... add forecasts and calculations ...
+ final ListenableFuture<List<List<Recommendation>>> filledRecommendations = Futures
+ .successfulAsList(Arrays.asList(
+ // Add Forecasts to Recommendations.
+ forecasts(recommendations),
+ // Add Forecasts to Recommendations.
+ calculations(recommendations)));
+
+ // ... and transform the list into agent response with filled recommendations.
+ return Futures
+ .transform(filledRecommendations, (AsyncFunction<List<List<Recommendation>>, AgentResponse>) input -> {
+ response.setRecommended(input.get(0));
+
+ return Futures.immediateFuture(response);
+ });
+ }
+
+ private ListenableFuture<List<Recommendation>> forecasts(final ListenableFuture<List<Recommendation>> recommendations) {
+ forecast.register(RxListenableFutureInvokerProvider.class);
+
+ // Fill the list with weather forecast.
+ return Futures.transform(recommendations, (AsyncFunction<List<Recommendation>, List<Recommendation>>) list ->
+ // For each recommendation ...
+ Futures.successfulAsList(Lists.transform(list, recommendation -> Futures.transform(
+ // ... get the weather forecast ...
+ forecast.resolveTemplate("destination", recommendation.getDestination()).request()
+ .rx(RxListenableFutureInvoker.class)
+ .get(Forecast.class),
+ // ... and set it to the recommendation.
+ (AsyncFunction<Forecast, Recommendation>) forecast -> {
+ recommendation.setForecast(forecast.getForecast());
+ return Futures.immediateFuture(recommendation);
+ }))));
+ }
+
+ private ListenableFuture<List<Recommendation>> calculations(final ListenableFuture<List<Recommendation>> recommendations) {
+ calculation.register(RxListenableFutureInvokerProvider.class);
+
+ // Fill the list with price calculations.
+ return Futures.transform(recommendations, (AsyncFunction<List<Recommendation>, List<Recommendation>>) list ->
+ // For each recommendation ...
+ Futures.successfulAsList(Lists.transform(list, recommendation -> Futures.transform(
+ // ... get the price calculation ...
+ calculation.resolveTemplate("from", "Moon")
+ .resolveTemplate("to", recommendation.getDestination())
+ .request().rx(RxListenableFutureInvoker.class).get(Calculation.class),
+ // ... and set it to the recommendation.
+ (AsyncFunction<Calculation, Recommendation>) calculation -> {
+ recommendation.setPrice(calculation.getPrice());
+ return Futures.immediateFuture(recommendation);
+ })))
+ );
+ }
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/ObservableAgentResource.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/ObservableAgentResource.java
new file mode 100644
index 0000000..f23aa66
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/ObservableAgentResource.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.agent;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.GenericType;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.client.rx.rxjava.RxObservableInvoker;
+import org.glassfish.jersey.client.rx.rxjava.RxObservableInvokerProvider;
+import org.glassfish.jersey.examples.rx.domain.AgentResponse;
+import org.glassfish.jersey.examples.rx.domain.Calculation;
+import org.glassfish.jersey.examples.rx.domain.Destination;
+import org.glassfish.jersey.examples.rx.domain.Forecast;
+import org.glassfish.jersey.examples.rx.domain.Recommendation;
+import org.glassfish.jersey.server.Uri;
+
+import rx.Observable;
+import rx.schedulers.Schedulers;
+
+/**
+ * Obtain information about visited (destination) and recommended (destination, forecast, price) places for "RxJava" user. Uses
+ * RxJava Observable and Jersey Client to obtain the data.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Singleton
+@Path("agent/observable")
+@Produces("application/json")
+public class ObservableAgentResource {
+
+ @Uri("remote/destination")
+ private WebTarget destination;
+
+ @Uri("remote/calculation/from/{from}/to/{to}")
+ private WebTarget calculation;
+
+ @Uri("remote/forecast/{destination}")
+ private WebTarget forecast;
+
+ @GET
+ public void observable(@Suspended final AsyncResponse async) {
+ final long time = System.nanoTime();
+ final Queue<String> errors = new ConcurrentLinkedQueue<>();
+
+ Observable.just(new AgentResponse())
+ // Obtain visited destinations.
+ .zipWith(visited(errors), (response, visited) -> {
+ response.setVisited(visited);
+ return response;
+ })
+ // Obtain recommended destinations. (does not depend on visited ones)
+ .zipWith(recommended(errors), (response, recommendations) -> {
+ response.setRecommended(recommendations);
+ return response;
+ })
+ // Observe on another thread than the one processing visited or recommended destinations.
+ .observeOn(Schedulers.io())
+ // Subscribe.
+ .subscribe(response -> {
+ // Do something with errors.
+
+ response.setProcessingTime((System.nanoTime() - time) / 1000000);
+ async.resume(response);
+ }, async::resume);
+ }
+
+ private Observable<List<Destination>> visited(final Queue<String> errors) {
+ destination.register(RxObservableInvokerProvider.class);
+
+ return destination.path("visited").request()
+ // Identify the user.
+ .header("Rx-User", "RxJava")
+ // Reactive invoker.
+ .rx(RxObservableInvoker.class)
+ // Return a list of destinations.
+ .get(new GenericType<List<Destination>>() {
+ })
+ // Handle Errors.
+ .onErrorReturn(throwable -> {
+ errors.offer("Visited: " + throwable.getMessage());
+ return Collections.emptyList();
+ });
+ }
+
+ private Observable<List<Recommendation>> recommended(final Queue<String> errors) {
+ destination.register(RxObservableInvokerProvider.class);
+
+ // Recommended places.
+ final Observable<Destination> recommended = destination.path("recommended").request()
+ // Identify the user.
+ .header("Rx-User", "RxJava")
+ // Reactive invoker.
+ .rx(RxObservableInvoker.class)
+ // Return a list of destinations.
+ .get(new GenericType<List<Destination>>() {
+ })
+ // Handle Errors.
+ .onErrorReturn(throwable -> {
+ errors.offer("Recommended: " + throwable
+ .getMessage());
+ return Collections.emptyList();
+ })
+ // Emit destinations one-by-one.
+ .flatMap(Observable::from)
+ // Remember emitted items for dependant requests.
+ .cache();
+
+ forecast.register(RxObservableInvokerProvider.class);
+
+ // Forecasts. (depend on recommended destinations)
+ final Observable<Forecast> forecasts = recommended.flatMap(destination ->
+ forecast
+ .resolveTemplate("destination", destination.getDestination())
+ .request().rx(RxObservableInvoker.class).get(Forecast.class)
+ .onErrorReturn(throwable -> {
+ errors.offer("Forecast: " + throwable.getMessage());
+ return new Forecast(destination.getDestination(), "N/A");
+ }));
+
+ calculation.register(RxObservableInvokerProvider.class);
+
+ // Calculations. (depend on recommended destinations)
+ final Observable<Calculation> calculations = recommended.flatMap(destination ->
+ calculation.resolveTemplate("from", "Moon").resolveTemplate("to", destination.getDestination())
+ .request().rx(RxObservableInvoker.class).get(Calculation.class)
+ .onErrorReturn(throwable -> {
+ errors.offer("Calculation: " + throwable.getMessage());
+ return new Calculation("Moon", destination.getDestination(), -1);
+ }));
+
+ return Observable.zip(recommended, forecasts, calculations, Recommendation::new).toList();
+ }
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/SyncAgentResource.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/SyncAgentResource.java
new file mode 100644
index 0000000..102d465
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/SyncAgentResource.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.agent;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.examples.rx.domain.AgentResponse;
+import org.glassfish.jersey.examples.rx.domain.Calculation;
+import org.glassfish.jersey.examples.rx.domain.Destination;
+import org.glassfish.jersey.examples.rx.domain.Forecast;
+import org.glassfish.jersey.examples.rx.domain.Recommendation;
+import org.glassfish.jersey.server.Uri;
+
+/**
+ * Obtain information about visited (destination) and recommended (destination, forecast, price) places for "Sync" user. Uses
+ * standard JAX-RS sync approach to obtain the data.
+ *
+ * @author Michal Gajdos
+ */
+@Path("agent/sync")
+@Produces("application/json")
+public class SyncAgentResource {
+
+ @Uri("remote/destination")
+ private WebTarget destination;
+
+ @Uri("remote/calculation/from/{from}/to/{to}")
+ private WebTarget calculation;
+
+ @Uri("remote/forecast/{destination}")
+ private WebTarget forecast;
+
+ @GET
+ public AgentResponse sync() {
+ final long time = System.nanoTime();
+
+ final AgentResponse response = new AgentResponse();
+ final Queue<String> errors = new ConcurrentLinkedQueue<>();
+
+ // Obtain visited destinations.
+ try {
+ response.setVisited(destination.path("visited").request()
+ // Identify the user.
+ .header("Rx-User", "Sync")
+ // Return a list of destinations
+ .get(new GenericType<List<Destination>>() {}));
+ } catch (final Throwable throwable) {
+ errors.offer("Visited: " + throwable.getMessage());
+ }
+
+ // Obtain recommended destinations. (does not depend on visited ones)
+ List<Destination> recommended = Collections.emptyList();
+ try {
+ recommended = destination.path("recommended").request()
+ // Identify the user.
+ .header("Rx-User", "Sync")
+ // Return a list of destinations.
+ .get(new GenericType<List<Destination>>() {});
+ } catch (final Throwable throwable) {
+ errors.offer("Recommended: " + throwable.getMessage());
+ }
+
+ // Forecasts. (depend on recommended destinations)
+ final Map<String, Forecast> forecasts = new HashMap<>();
+ for (final Destination dest : recommended) {
+ try {
+ forecasts.put(dest.getDestination(),
+ forecast.resolveTemplate("destination", dest.getDestination()).request().get(Forecast.class));
+ } catch (final Throwable throwable) {
+ errors.offer("Forecast: " + throwable.getMessage());
+ }
+ }
+
+ // Calculations. (depend on recommended destinations)
+ final Map<String, Calculation> calculations = new HashMap<>();
+ recommended.stream().forEach(destination -> {
+ try {
+ calculations.put(destination.getDestination(), calculation.resolveTemplate("from", "Moon")
+ .resolveTemplate("to", destination.getDestination())
+ .request().get(Calculation.class));
+ } catch (final Throwable throwable) {
+ errors.offer("Calculation: " + throwable.getMessage());
+ }
+ });
+
+ // Recommendations.
+ final List<Recommendation> recommendations = new ArrayList<>(recommended.size());
+ for (final Destination dest : recommended) {
+ final Forecast fore = forecasts.get(dest.getDestination());
+ final Calculation calc = calculations.get(dest.getDestination());
+
+ recommendations.add(new Recommendation(dest.getDestination(),
+ fore != null ? fore.getForecast() : "N/A", calc != null ? calc.getPrice() : -1));
+ }
+
+ // Do something with errors.
+ // ...
+
+ response.setRecommended(recommendations);
+ response.setProcessingTime((System.nanoTime() - time) / 1000000);
+ return response;
+ }
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/AgentResponse.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/AgentResponse.java
new file mode 100644
index 0000000..4b656d6
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/AgentResponse.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Michal Gajdos
+ */
+public class AgentResponse {
+
+ private List<Destination> visited = new ArrayList<>();
+ private List<Recommendation> recommended;
+ private long processingTime;
+
+ public AgentResponse() {
+ }
+
+ public List<Destination> getVisited() {
+ return visited;
+ }
+
+ public void setVisited(final List<Destination> visited) {
+ this.visited = visited;
+ }
+
+ public void setRecommended(final List<Recommendation> recommended) {
+ this.recommended = recommended;
+ }
+
+ public List<Recommendation> getRecommended() {
+ return recommended;
+ }
+
+ public void setProcessingTime(final long processingTime) {
+ this.processingTime = processingTime;
+ }
+
+ public long getProcessingTime() {
+ return processingTime;
+ }
+
+ public AgentResponse visited(final List<Destination> visited) {
+ setVisited(visited);
+ return this;
+ }
+
+ public AgentResponse recommended(final List<Recommendation> recommended) {
+ setRecommended(recommended);
+ return this;
+ }
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/Calculation.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/Calculation.java
new file mode 100644
index 0000000..1d5a141
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/Calculation.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.domain;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @author Michal Gajdos
+ */
+@XmlRootElement
+public class Calculation {
+
+ private String from;
+ private String to;
+ private int price;
+
+ public Calculation() {
+ }
+
+ public Calculation(final String from, final String to, final int price) {
+ this.from = from;
+ this.to = to;
+ this.price = price;
+ }
+
+ public String getFrom() {
+ return from;
+ }
+
+ public void setFrom(final String from) {
+ this.from = from;
+ }
+
+ public String getTo() {
+ return to;
+ }
+
+ public void setTo(final String to) {
+ this.to = to;
+ }
+
+ public int getPrice() {
+ return price;
+ }
+
+ public void setPrice(final int price) {
+ this.price = price;
+ }
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/Destination.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/Destination.java
new file mode 100644
index 0000000..f5a4276
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/Destination.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.domain;
+
+/**
+ * @author Michal Gajdos
+ */
+public class Destination {
+
+ private String destination;
+
+ public Destination() {
+ }
+
+ public Destination(final String destination) {
+ this.destination = destination;
+ }
+
+ public String getDestination() {
+ return destination;
+ }
+
+ public void setDestination(final String destination) {
+ this.destination = destination;
+ }
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/Forecast.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/Forecast.java
new file mode 100644
index 0000000..95ee20c
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/Forecast.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.domain;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @author Michal Gajdos
+ */
+@XmlRootElement
+public class Forecast {
+
+ private String forecast;
+ private String destination;
+
+ public Forecast() {
+ }
+
+ public Forecast(final String destination, final String forecast) {
+ this.destination = destination;
+ this.forecast = forecast;
+ }
+
+ public String getForecast() {
+ return forecast;
+ }
+
+ public void setForecast(final String forecast) {
+ this.forecast = forecast;
+ }
+
+ public String getDestination() {
+ return destination;
+ }
+
+ public void setDestination(final String destination) {
+ this.destination = destination;
+ }
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/Recommendation.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/Recommendation.java
new file mode 100644
index 0000000..3c2c7ff
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/domain/Recommendation.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.domain;
+
+/**
+ * @author Michal Gajdos
+ */
+public class Recommendation {
+
+ private String destination;
+ private String forecast;
+ private int price;
+
+ public Recommendation() {
+ }
+
+ public Recommendation(final String destination) {
+ this.destination = destination;
+ }
+
+ public Recommendation(final String destination, final String forecast, final int price) {
+ this.destination = destination;
+ this.forecast = forecast;
+ this.price = price;
+ }
+
+ public Recommendation(final Destination destination) {
+ this.destination = destination.getDestination();
+ }
+
+ public Recommendation(final Destination destination, final Forecast forecast, final Calculation calculation) {
+ this.destination = destination.getDestination();
+ this.forecast = forecast.getForecast();
+ this.price = calculation.getPrice();
+ }
+
+ public String getDestination() {
+ return destination;
+ }
+
+ public void setDestination(final String destination) {
+ this.destination = destination;
+ }
+
+ public String getForecast() {
+ return forecast;
+ }
+
+ public void setForecast(final String forecast) {
+ this.forecast = forecast;
+ }
+
+ public int getPrice() {
+ return price;
+ }
+
+ public void setPrice(final int price) {
+ this.price = price;
+ }
+
+ public Recommendation forecast(final Forecast forecast) {
+ setForecast(forecast.getForecast());
+ return this;
+ }
+
+ public Recommendation calculation(final Calculation calculation) {
+ setPrice(calculation.getPrice());
+ return this;
+ }
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/remote/CalculationResource.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/remote/CalculationResource.java
new file mode 100644
index 0000000..84792ea
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/remote/CalculationResource.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.remote;
+
+import java.util.Random;
+
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+
+import org.glassfish.jersey.examples.rx.Helper;
+import org.glassfish.jersey.examples.rx.domain.Calculation;
+import org.glassfish.jersey.server.ManagedAsync;
+
+/**
+ * Obtain a calculation for a trip from one destination to another.
+ *
+ * @author Michal Gajdos
+ */
+@Path("remote/calculation")
+@Produces("application/json")
+public class CalculationResource {
+
+ @GET
+ @ManagedAsync
+ @Path("/from/{from}/to/{to}")
+ public Calculation calculation(@PathParam("from") @DefaultValue("Moon") final String from,
+ @PathParam("to") final String to) {
+ // Simulate long-running operation.
+ Helper.sleep(350);
+
+ return new Calculation(from, to, new Random().nextInt(10000));
+ }
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/remote/DestinationResource.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/remote/DestinationResource.java
new file mode 100644
index 0000000..6fa1d15
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/remote/DestinationResource.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.remote;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.examples.rx.Helper;
+import org.glassfish.jersey.examples.rx.domain.Destination;
+import org.glassfish.jersey.internal.util.collection.Views;
+import org.glassfish.jersey.server.ManagedAsync;
+
+/**
+ * Obtain a list of visited / recommended places for a given user.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+@Path("remote/destination")
+@Produces("application/json")
+public class DestinationResource {
+
+ private static final Map<String, List<String>> VISITED = new HashMap<>();
+
+ static {
+ VISITED.put("Sync", Helper.getCountries(5));
+ VISITED.put("Async", Helper.getCountries(5));
+ VISITED.put("Guava", Helper.getCountries(5));
+ VISITED.put("RxJava", Helper.getCountries(5));
+ VISITED.put("RxJava2", Helper.getCountries(5));
+ VISITED.put("CompletionStage", Helper.getCountries(5));
+ }
+
+ @GET
+ @ManagedAsync
+ @Path("visited")
+ public List<Destination> visited(@HeaderParam("Rx-User") @DefaultValue("KO") final String user) {
+ // Simulate long-running operation.
+ Helper.sleep();
+
+ if (!VISITED.containsKey(user)) {
+ VISITED.put(user, Helper.getCountries(5));
+ }
+
+ return Views.listView(VISITED.get(user), Destination::new);
+ }
+
+ @GET
+ @ManagedAsync
+ @Path("recommended")
+ public List<Destination> recommended(@HeaderParam("Rx-User") @DefaultValue("KO") final String user,
+ @QueryParam("limit") @DefaultValue("5") final int limit) {
+ // Simulate long-running operation.
+ Helper.sleep();
+
+ if (!VISITED.containsKey(user)) {
+ VISITED.put(user, Helper.getCountries(5));
+ }
+
+ return Views.listView(Helper.getCountries(limit, VISITED.get(user)), Destination::new);
+ }
+}
diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/remote/ForecastResource.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/remote/ForecastResource.java
new file mode 100644
index 0000000..09247fe
--- /dev/null
+++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/remote/ForecastResource.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx.remote;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+
+import org.glassfish.jersey.examples.rx.Helper;
+import org.glassfish.jersey.examples.rx.domain.Forecast;
+import org.glassfish.jersey.server.ManagedAsync;
+
+/**
+ * Obtain current weather conditions in a destination.
+ *
+ * @author Michal Gajdos
+ */
+@Path("remote/forecast")
+@Produces("application/json")
+public class ForecastResource {
+
+ @GET
+ @ManagedAsync
+ @Path("/{destination}")
+ public Forecast forecast(@PathParam("destination") final String destination) {
+ // Simulate long-running operation.
+ Helper.sleep(350);
+
+ return new Forecast(destination, Helper.getForecast());
+ }
+}
diff --git a/examples/rx-client-webapp/src/test/java/org/glassfish/jersey/examples/rx/RxClientsTest.java b/examples/rx-client-webapp/src/test/java/org/glassfish/jersey/examples/rx/RxClientsTest.java
new file mode 100644
index 0000000..0d6ae12
--- /dev/null
+++ b/examples/rx-client-webapp/src/test/java/org/glassfish/jersey/examples/rx/RxClientsTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.rx;
+
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.examples.rx.domain.AgentResponse;
+import org.glassfish.jersey.test.DeploymentContext;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.ServletDeploymentContext;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Invoke clients in Agent part of the application.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class RxClientsTest extends JerseyTest {
+
+ @Override
+ protected DeploymentContext configureDeployment() {
+ return ServletDeploymentContext.builder(RxApplication.class)
+ .contextPath("rx-client-webapp/rx").build();
+ }
+
+ @Test
+ public void testSyncClient() throws Exception {
+ // warmup
+ target("agent").path("sync").request().get();
+
+ final Response response = target("agent").path("sync").request().get();
+ response.bufferEntity();
+
+ final AgentResponse agentResponse = response.readEntity(AgentResponse.class);
+
+ assertThat(agentResponse.getVisited().size(), is(5));
+ assertThat(agentResponse.getRecommended().size(), is(5));
+
+ assertThat(agentResponse.getProcessingTime() > 4500, is(true));
+
+ System.out.println(response.readEntity(String.class));
+ System.out.println("Processing Time: " + agentResponse.getProcessingTime());
+ }
+
+ @Test
+ public void testAsyncClient() throws Exception {
+ // warmup
+ target("agent").path("async").request().get();
+
+ final Response response = target("agent").path("async").request().get();
+ response.bufferEntity();
+
+ final AgentResponse agentResponse = response.readEntity(AgentResponse.class);
+
+ assertThat(agentResponse.getVisited().size(), is(5));
+ assertThat(agentResponse.getRecommended().size(), is(5));
+
+ assertThat(agentResponse.getProcessingTime() > 850, is(true));
+ assertThat(agentResponse.getProcessingTime() < 4500, is(true));
+
+ System.out.println(response.readEntity(String.class));
+ System.out.println("Processing Time: " + agentResponse.getProcessingTime());
+ }
+
+ @Test
+ public void testRxObservableClient() throws Exception {
+ // warmup
+ target("agent").path("observable").request().get();
+
+ final Response response = target("agent").path("observable").request().get();
+ response.bufferEntity();
+
+ final AgentResponse agentResponse = response.readEntity(AgentResponse.class);
+
+ assertThat(agentResponse.getVisited().size(), is(5));
+ assertThat(agentResponse.getRecommended().size(), is(5));
+
+ assertThat(agentResponse.getProcessingTime() > 850, is(true));
+ assertThat(agentResponse.getProcessingTime() < 4500, is(true));
+
+ System.out.println(response.readEntity(String.class));
+ System.out.println("Processing Time: " + agentResponse.getProcessingTime());
+ }
+
+ @Test
+ public void testRxFlowableClient() throws Exception {
+ // warmup
+ target("agent").path("flowable").request().get();
+
+ final Response response = target("agent").path("flowable").request().get();
+ response.bufferEntity();
+
+ final AgentResponse agentResponse = response.readEntity(AgentResponse.class);
+
+ assertThat(agentResponse.getVisited().size(), is(5));
+ assertThat(agentResponse.getRecommended().size(), is(5));
+
+ assertThat(agentResponse.getProcessingTime() > 850, is(true));
+ assertThat(agentResponse.getProcessingTime() < 4500, is(true));
+
+ System.out.println(response.readEntity(String.class));
+ System.out.println("Processing Time: " + agentResponse.getProcessingTime());
+ }
+
+ @Test
+ public void testRxCompletionStageClient() throws Exception {
+ // warmup
+ target("agent").path("completion").request().get();
+
+ final Response response = target("agent").path("completion").request().get();
+ response.bufferEntity();
+
+ final AgentResponse agentResponse = response.readEntity(AgentResponse.class);
+
+ assertThat(agentResponse.getVisited().size(), is(5));
+ assertThat(agentResponse.getRecommended().size(), is(5));
+
+ assertThat(agentResponse.getProcessingTime() > 850, is(true));
+ assertThat(agentResponse.getProcessingTime() < 4500, is(true));
+
+ System.out.println(response.readEntity(String.class));
+ System.out.println("Processing Time: " + agentResponse.getProcessingTime());
+ }
+}
diff --git a/examples/server-async-managed/README.MD b/examples/server-async-managed/README.MD
new file mode 100644
index 0000000..1c63f4c
--- /dev/null
+++ b/examples/server-async-managed/README.MD
@@ -0,0 +1,74 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Server Asynchronous Managed Example
+===================================
+
+This example demonstrates JAX-RS 2.0 server-side non-blocking API using **ManagedAsync** annotation.
+
+The full description how to create an asynchronous resource can be found in Jersey User Guide, chapter
+[Asynchronous Services and Clients](https://jersey.java.net/documentation/latest/async.html).
+
+Indicates that the resource method to which the annotation has been applied
+ * should be executed on a separate thread managed by an internal Jersey
+
+The goal of the sample is to demonstrate that with limited I/O processing threads
+on the server the synchronous execution of a long-running task may lead to resource
+starvation caused by I/O processing threads being blocked by the long-running
+operation, unable to serve new incoming requests.
+
+OTOH, when the same long-running operation is executed asynchronously, the I/O
+threads do not need to block while waiting for the long-running operation to finish
+and the overall throughput of the system is greatly increased.
+
+- Look at the another example how to create a non-blocking API without **ManagedAsync**
+annotation <https://github.com/jersey/jersey/tree/master/examples/server-async>
+which also shown the way how to provide an immediate response for a client
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+--------------------------------| --------------------------| --------------
+**_chat_** | ChatResource | POST
+**_chat_** | ChatResource | GET
+**_managedasync/longrunning_** | BlockingPostChatResource | POST
+
+Sample Response
+---------------
+
+This section shortly describes what really happens during the calls in this chat example.
+
+We can start with POST request:
+```
+curl -v -X POST http://localhost:8080/base/chat -H "Content-Type: application/json" -d '{
+ "author": "Anonymous Author",
+ "message": "Secret Message"
+}'
+```
+
+We can notice that we haven't got any response yet. The called method is annotated **@ManagedAsync**
+which means that the I/O thread delegates a method processing to another Thread-Pool and is ready
+to accept another incoming request. But the processing is still stop. Why? **AsyncResponse** was added
+to the blocking queue and now it's waiting for GET call which will take the response and will finish the processing
+of the preceding call.
+
+```
+curl -v -X GET http://localhost:8080/base/chat
+```
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
diff --git a/examples/server-async-managed/pom.xml b/examples/server-async-managed/pom.xml
new file mode 100644
index 0000000..60905bb
--- /dev/null
+++ b/examples/server-async-managed/pom.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>server-async-managed</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-server-async-managed</name>
+
+ <description>Jersey JAX-RS asynchronous server-side example with custom Jersey executor providers.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jackson</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.server.async.managed.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/server-async-managed/src/main/java/org/glassfish/jersey/examples/server/async/managed/App.java b/examples/server-async-managed/src/main/java/org/glassfish/jersey/examples/server/async/managed/App.java
new file mode 100644
index 0000000..3171046
--- /dev/null
+++ b/examples/server-async-managed/src/main/java/org/glassfish/jersey/examples/server/async/managed/App.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async.managed;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+import static org.glassfish.jersey.logging.LoggingFeature.Verbosity.PAYLOAD_ANY;
+
+/**
+ * Jersey example application for custom executors managed async resources.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/base/");
+ public static final String ASYNC_LONG_RUNNING_MANAGED_OP_PATH = "managedasync/longrunning";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Custom Executor Managed Async Resources\" Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, create(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.\n"
+ + "To test long-running asynchronous operation resource, try %s%s\n"
+ + "To test async chat resource, try %s%s\n"
+ + "Stop the application using CTRL+C", BASE_URI, ASYNC_LONG_RUNNING_MANAGED_OP_PATH, BASE_URI, "chat"));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ public static ResourceConfig create() {
+ return new ResourceConfig()
+ .registerClasses(JacksonFeature.class, ChatResource.class, SimpleJerseyExecutorManagedLongRunningResource.class)
+ .registerInstances(new LoggingFeature(Logger.getLogger(App.class.getName()), PAYLOAD_ANY));
+ }
+}
diff --git a/examples/server-async-managed/src/main/java/org/glassfish/jersey/examples/server/async/managed/ChatResource.java b/examples/server-async-managed/src/main/java/org/glassfish/jersey/examples/server/async/managed/ChatResource.java
new file mode 100644
index 0000000..5c86ad7
--- /dev/null
+++ b/examples/server-async-managed/src/main/java/org/glassfish/jersey/examples/server/async/managed/ChatResource.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async.managed;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+
+import org.glassfish.jersey.server.ManagedAsync;
+
+/**
+ * Example of a simple fire&forget point-to-point messaging resource.
+ *
+ * This version of the messaging resource does not block when POSTing a new message.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("chat")
+@Produces("application/json")
+public class ChatResource {
+
+ private static final BlockingQueue<AsyncResponse> suspended = new ArrayBlockingQueue<AsyncResponse>(5);
+
+ @GET
+ @ManagedAsync
+ public void getMessage(@Suspended final AsyncResponse ar) throws InterruptedException {
+ suspended.put(ar);
+ }
+
+ @POST
+ @ManagedAsync
+ public String postMessage(final Message message) throws InterruptedException {
+ final AsyncResponse asyncResponse = suspended.take();
+ asyncResponse.resume(message);
+ return "Sent!";
+ }
+}
diff --git a/examples/server-async-managed/src/main/java/org/glassfish/jersey/examples/server/async/managed/Message.java b/examples/server-async-managed/src/main/java/org/glassfish/jersey/examples/server/async/managed/Message.java
new file mode 100644
index 0000000..7f526d7
--- /dev/null
+++ b/examples/server-async-managed/src/main/java/org/glassfish/jersey/examples/server/async/managed/Message.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async.managed;
+
+import java.util.Date;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * Chat message JAXB POJO.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@XmlRootElement
+public class Message {
+
+ public String author = "";
+ public String message = "";
+ public long time = new Date().getTime();
+
+ public Message(String author, String message) {
+ this.author = author;
+ this.message = message;
+ }
+
+ public Message() {
+ }
+
+ @Override
+ public String toString() {
+ return "Message{" + "author=" + author + ", message=" + message + ", time=" + time + '}';
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Message other = (Message) obj;
+ if ((this.author == null) ? (other.author != null) : !this.author.equals(other.author)) {
+ return false;
+ }
+ if ((this.message == null) ? (other.message != null) : !this.message.equals(other.message)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 47 * hash + (this.author != null ? this.author.hashCode() : 0);
+ hash = 47 * hash + (this.message != null ? this.message.hashCode() : 0);
+ return hash;
+ }
+}
diff --git a/examples/server-async-managed/src/main/java/org/glassfish/jersey/examples/server/async/managed/SimpleJerseyExecutorManagedLongRunningResource.java b/examples/server-async-managed/src/main/java/org/glassfish/jersey/examples/server/async/managed/SimpleJerseyExecutorManagedLongRunningResource.java
new file mode 100644
index 0000000..79f2851
--- /dev/null
+++ b/examples/server-async-managed/src/main/java/org/glassfish/jersey/examples/server/async/managed/SimpleJerseyExecutorManagedLongRunningResource.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async.managed;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+
+import org.glassfish.jersey.server.ManagedAsync;
+
+/**
+ * Example of a simple resource with a long-running operation executed in a
+ * custom Jersey container request processing thread.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path(App.ASYNC_LONG_RUNNING_MANAGED_OP_PATH)
+@Produces("text/plain")
+public class SimpleJerseyExecutorManagedLongRunningResource {
+
+ public static final String NOTIFICATION_RESPONSE = "Hello async world!";
+ //
+ private static final Logger LOGGER = Logger.getLogger(SimpleJerseyExecutorManagedLongRunningResource.class.getName());
+ private static final int SLEEP_TIME_IN_MILLIS = 1000;
+
+ @GET
+ @ManagedAsync
+ public void longGet(@Suspended final AsyncResponse ar, @QueryParam("id") int requestId) {
+ try {
+ Thread.sleep(SLEEP_TIME_IN_MILLIS);
+ } catch (InterruptedException ex) {
+ LOGGER.log(Level.SEVERE, "Response processing interrupted", ex);
+ }
+ ar.resume(requestId + " - " + NOTIFICATION_RESPONSE);
+ }
+}
diff --git a/examples/server-async-managed/src/test/java/org/glassfish/jersey/examples/server/async/managed/ManagedAsyncResourceTest.java b/examples/server-async-managed/src/test/java/org/glassfish/jersey/examples/server/async/managed/ManagedAsyncResourceTest.java
new file mode 100644
index 0000000..3eb6234
--- /dev/null
+++ b/examples/server-async-managed/src/test/java/org/glassfish/jersey/examples/server/async/managed/ManagedAsyncResourceTest.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async.managed;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test for the asynchronous managed resources example.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class ManagedAsyncResourceTest extends JerseyTest {
+
+ private static final Logger LOGGER = Logger.getLogger(ManagedAsyncResourceTest.class.getName());
+
+ @Override
+ protected ResourceConfig configure() {
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.external.ExternalTestContainerFactory
+
+ enable(TestProperties.LOG_TRAFFIC);
+ enable(TestProperties.DUMP_ENTITY);
+ return App.create();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(new JacksonFeature());
+ }
+
+ @Test
+ public void testLongRunningResource() throws InterruptedException {
+ final WebTarget resourceTarget = target().path(App.ASYNC_LONG_RUNNING_MANAGED_OP_PATH);
+ final String expectedResponse = SimpleJerseyExecutorManagedLongRunningResource.NOTIFICATION_RESPONSE;
+
+ final int MAX_MESSAGES = 100;
+ final int LATCH_WAIT_TIMEOUT = 10 * getAsyncTimeoutMultiplier();
+ final boolean debugMode = false;
+ final boolean sequentialGet = false;
+ final Object sequentialGetLock = new Object();
+
+ final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+ .setNameFormat("async-resource-test-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+
+ final Map<Integer, String> getResponses = new ConcurrentHashMap<Integer, String>();
+
+ final CountDownLatch getRequestLatch = new CountDownLatch(MAX_MESSAGES);
+
+ try {
+ for (int i = 0; i < MAX_MESSAGES; i++) {
+ final int requestId = i;
+ executor.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ if (debugMode || sequentialGet) {
+ synchronized (sequentialGetLock) {
+ get();
+ }
+ } else {
+ get();
+ }
+ }
+
+ private void get() {
+ try {
+ int attemptCounter = 0;
+ while (true) {
+ attemptCounter++;
+ try {
+ final String response = resourceTarget.queryParam("id", requestId).request()
+ .get(String.class);
+ getResponses.put(requestId, response);
+ break;
+ } catch (Throwable t) {
+ LOGGER.log(Level.SEVERE, String.format("Error sending GET request <%s> for %d. time.",
+ requestId, attemptCounter), t);
+ }
+ if (attemptCounter > 3) {
+ break;
+ }
+ Thread.sleep(10);
+ }
+ } catch (InterruptedException ignored) {
+ LOGGER.log(Level.WARNING,
+ String.format("Error sending GET message <%s>: Interrupted", requestId), ignored);
+ } finally {
+ getRequestLatch.countDown();
+ }
+ }
+ });
+ }
+
+ if (debugMode) {
+ getRequestLatch.await();
+ } else {
+ if (!getRequestLatch.await(LATCH_WAIT_TIMEOUT, TimeUnit.SECONDS)) {
+ LOGGER.log(Level.SEVERE, "Waiting for all GET requests to complete has timed out.");
+ }
+ }
+ } finally {
+ executor.shutdownNow();
+ }
+
+ StringBuilder messageBuilder = new StringBuilder("GET responses received: ").append(getResponses.size()).append("\n");
+ for (Map.Entry<Integer, String> getResponseEntry : getResponses.entrySet()) {
+ messageBuilder.append("GET response for message ")
+ .append(getResponseEntry.getKey()).append(": ")
+ .append(getResponseEntry.getValue()).append('\n');
+ }
+ LOGGER.info(messageBuilder.toString());
+
+ for (Map.Entry<Integer, String> entry : getResponses.entrySet()) {
+ assertTrue(
+ "Unexpected GET notification response for message " + entry.getKey(),
+ entry.getValue().contains(expectedResponse));
+ }
+ assertEquals(MAX_MESSAGES, getResponses.size());
+ }
+
+ @Test
+ public void testChatResource() throws InterruptedException {
+ final WebTarget resourceTarget = target().path("chat");
+ final int MAX_MESSAGES = 100;
+ final int LATCH_WAIT_TIMEOUT = 10 * getAsyncTimeoutMultiplier();
+ final boolean debugMode = false;
+ final boolean sequentialGet = false;
+ final boolean sequentialPost = false;
+ final Object sequentialGetLock = new Object();
+ final Object sequentialPostLock = new Object();
+
+ final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+ .setNameFormat("async-resource-test-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+
+ final Map<Integer, Integer> postResponses = new ConcurrentHashMap<Integer, Integer>();
+ final Map<Integer, Message> getResponses = new ConcurrentHashMap<Integer, Message>();
+
+ final CountDownLatch postRequestLatch = new CountDownLatch(MAX_MESSAGES);
+ final CountDownLatch getRequestLatch = new CountDownLatch(MAX_MESSAGES);
+
+ try {
+ for (int i = 0; i < MAX_MESSAGES; i++) {
+ final int requestId = i;
+ executor.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ if (debugMode || sequentialPost) {
+ synchronized (sequentialPostLock) {
+ post();
+ }
+ } else {
+ post();
+ }
+ }
+
+ private void post() {
+ try {
+ int attemptCounter = 0;
+ while (true) {
+ attemptCounter++;
+ try {
+ final Response response = resourceTarget.request()
+ .post(Entity.json(new Message("" + requestId, "" + requestId)));
+ postResponses.put(requestId, response.getStatus());
+ break;
+ } catch (Throwable t) {
+ LOGGER.log(Level.WARNING, String.format("Error POSTING message <%s> for %d. time.",
+ requestId, attemptCounter), t);
+ }
+ if (attemptCounter > 3) {
+ break;
+ }
+ Thread.sleep(10);
+ }
+ } catch (InterruptedException ignored) {
+ LOGGER.log(Level.WARNING,
+ String.format("Error POSTING message <%s>: Interrupted", requestId), ignored);
+ } finally {
+ postRequestLatch.countDown();
+ }
+ }
+ });
+ executor.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ if (debugMode || sequentialGet) {
+ synchronized (sequentialGetLock) {
+ get();
+ }
+ } else {
+ get();
+ }
+ }
+
+ private void get() {
+ try {
+ int attemptCounter = 0;
+ while (true) {
+ attemptCounter++;
+ try {
+ final Message response = resourceTarget.request("application/json").get(Message.class);
+ getResponses.put(requestId, response);
+ break;
+ } catch (Throwable t) {
+ LOGGER.log(Level.SEVERE, String.format("Error sending GET request <%s> for %d. time.",
+ requestId, attemptCounter), t);
+ }
+ if (attemptCounter > 3) {
+ break;
+ }
+ Thread.sleep(10);
+ }
+ } catch (InterruptedException ignored) {
+ LOGGER.log(Level.WARNING,
+ String.format("Error sending GET message <%s>: Interrupted", requestId), ignored);
+ } finally {
+ getRequestLatch.countDown();
+ }
+ }
+ });
+ }
+
+ if (debugMode) {
+ postRequestLatch.await();
+ getRequestLatch.await();
+ } else {
+ if (!postRequestLatch.await(LATCH_WAIT_TIMEOUT, TimeUnit.SECONDS)) {
+ LOGGER.log(Level.SEVERE, "Waiting for all POST requests to complete has timed out.");
+ }
+ if (!getRequestLatch.await(LATCH_WAIT_TIMEOUT, TimeUnit.SECONDS)) {
+ LOGGER.log(Level.SEVERE, "Waiting for all GET requests to complete has timed out.");
+ }
+ }
+ } finally {
+ executor.shutdownNow();
+ }
+
+ StringBuilder messageBuilder = new StringBuilder("POST responses received: ").append(postResponses.size()).append("\n");
+ for (Map.Entry<Integer, Integer> postResponseEntry : postResponses.entrySet()) {
+ messageBuilder.append("POST response for message ")
+ .append(postResponseEntry.getKey()).append(": ")
+ .append(postResponseEntry.getValue()).append('\n');
+ }
+ messageBuilder.append('\n');
+ messageBuilder.append("GET responses received: ").append(getResponses.size()).append("\n");
+ for (Map.Entry<Integer, Message> getResponseEntry : getResponses.entrySet()) {
+ messageBuilder.append("GET response for message ")
+ .append(getResponseEntry.getKey()).append(": ")
+ .append(getResponseEntry.getValue()).append('\n');
+ }
+ LOGGER.info(messageBuilder.toString());
+
+ for (Map.Entry<Integer, Integer> postResponseEntry : postResponses.entrySet()) {
+ assertEquals(
+ "Unexpected POST notification response for message " + postResponseEntry.getKey(),
+ 200, postResponseEntry.getValue().intValue());
+ }
+
+ final List<Integer> lost = new LinkedList<Integer>();
+ final Collection<Message> getResponseValues = getResponses.values();
+ for (int i = 0; i < MAX_MESSAGES; i++) {
+ if (!getResponseValues.contains(new Message("" + i, "" + i))) {
+ lost.add(i);
+ }
+ }
+ if (!lost.isEmpty()) {
+ fail("Detected a posted message loss(es): " + lost.toString());
+ }
+ assertEquals(MAX_MESSAGES, postResponses.size());
+ assertEquals(MAX_MESSAGES, getResponses.size());
+ }
+}
diff --git a/examples/server-async-standalone/README.MD b/examples/server-async-standalone/README.MD
new file mode 100644
index 0000000..d5add0b
--- /dev/null
+++ b/examples/server-async-standalone/README.MD
@@ -0,0 +1,104 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Server Asynchronous Standalone Example
+======================================
+
+This example demonstrates JAX-RS 2.0 asynchronous API, both on the client and
+server side.
+
+The goal of the sample is to demonstrate that with limited I/O processing threads
+on the server the synchronous execution of a long-running task may lead to resource
+starvation caused by I/O processing threads being blocked by the long-running
+operation, unable to serve new incoming requests.
+
+OTOH, when the same long-running operation is executed asynchronously, the I/O
+threads do not need to block while waiting for the long-running operation to finish
+and the overall throughput of the system is greatly increased.
+
+The example consists of 2 modules:
+
+ 1. web application module "webapp" (produces server-async-standalone-webapp.war)
+
+ - the module contains an long-running echo service deployed under application context root on path templates:
+
+ - "long-running/sync/{echo}", served by a synchronously executed, blockingmethod.
+
+ - "long-running/async/{echo}", served by an asynchronously executed, non-blocking method.
+
+ 2. client module "client" (produces client.jar)
+
+ - the module contains the web application client developed using JAX-RS 2.0
+ client API & leveraging the asynchronous client execution for spawning
+ multiple simultaneous client requests with subsequent asynchronous response
+ processing.
+
+To build the example, change to the example root directory and invoke maven build:
+
+> mvn clean package
+
+This command builds the web application WAR as well as client JAR artifacts. Once
+built, the web application may be deployed to the application server. E.g. to deploy
+the application to a running GlassFish instance, you may do (while still being in
+the root example directory):
+
+> $AS_HOME/bin/as-admin deploy ./webapp/target/server-async-standalone-webapp.war
+
+Once deployed, the client can be run as follows:
+
+> cd client
+> mvn exec:java
+
+This will start the main client GUI window. In the top panel, the GUI lets you to customize
+few variables such as number of requests to be sent, base web application URL or sync/async
+endpoint to be invoked. Once the client is set up, the "Run" button runs the requests. Each
+request is represented as a colored box in the lower window panel. The color indicates the
+request status:
+
+* yellow - running
+* green - successfully completed
+* red - request execution failed with an error
+
+Also, the tooltip of each box provides additional status information. After the execution is
+finished, short statistics about the run are displayed in the middle window panel.
+
+Executing command-line client:
+------------------------------
+Optionally, a command line Main.java client can be executed (by changing the mainClass of
+exec-maven-plugin in pom.xml to org.glassfish.jersey.examples.server.async.Main).
+Following properties may be customized in the command-line client runtime:
+
+ 1. req=<n>
+
+ : number of requests to be sent by the client, defaults to 10
+
+ 2. mode=(sync|async)
+
+ : execution mode defines which version of the service should be used, defaults to "async"
+
+ 3. uri=<base_webapp_uri>
+
+ : base URI of the deployed web application, defaults to "http://localhost:8080/server-async-standalone-webapp"
+
+These properties may be passed throug maven to the client runtime as command-line arguments
+using the -Dexec.args="<arguments>" flag, e.g.:
+
+> mvn exec:java -Dexec.args="req=20 mode=sync uri=http://foo.com/bar"
+
+When the example client is run, successfully executed requests are output as an
+asterisk '*', failed requests as an exclamation mark '!'. Any errors that occured
+during the client execution are reported at the end of the run including the
+statistics about the processing time and success ratio.
+
+Try running the client using many requests and compare the results when running
+against sync and async version of the resource, e.g.:
+
+> mvn exec:java -Dexec.args="req=100 mode=sync"
+> mvn exec:java -Dexec.args="req=100 mode=async"
+
+You should see a significant speed improvement in the latter run.
diff --git a/examples/server-async-standalone/client/pom.xml b/examples/server-async-standalone/client/pom.xml
new file mode 100644
index 0000000..06f779b
--- /dev/null
+++ b/examples/server-async-standalone/client/pom.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>server-async-standalone</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>server-async-standalone-client</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-server-async-standalone-client</name>
+
+ <description>Standalone Jersey JAX-RS asynchronous server-side processing example client.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.swinglabs</groupId>
+ <artifactId>swing-layout</artifactId>
+ <version>1.0.3</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <index>true</index>
+ <manifest>
+ <addClasspath>true</addClasspath>
+ <mainClass>org.glassfish.jersey.examples.server.async.MainWindow</mainClass>
+ </manifest>
+ </archive>
+ </configuration>
+ </plugin>
+ <!-- Run the application using "mvn exec:java" -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.server.async.MainWindow</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>run-external-tests</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <jersey.config.test.container.factory>${external.container.factory}</jersey.config.test.container.factory>
+ <jersey.config.test.container.port>${external.container.port}</jersey.config.test.container.port>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <properties>
+ <!-- External test container configuration is done via properties to allow overriding via command line. -->
+ <external.container.factory>org.glassfish.jersey.test.external.ExternalTestContainerFactory</external.container.factory>
+ <external.container.port>8080</external.container.port>
+ <maven.test.skip>false</maven.test.skip>
+ </properties>
+ </profile>
+ <profile>
+ <id>release</id>
+ <!-- do not create source zip bundles -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <skipAssembly>true</skipAssembly>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <properties>
+ <maven.test.skip>true</maven.test.skip>
+ </properties>
+
+</project>
diff --git a/examples/server-async-standalone/client/src/main/java/org/glassfish/jersey/examples/server/async/Main.java b/examples/server-async-standalone/client/src/main/java/org/glassfish/jersey/examples/server/async/Main.java
new file mode 100644
index 0000000..8f7c291
--- /dev/null
+++ b/examples/server-async-standalone/client/src/main/java/org/glassfish/jersey/examples/server/async/Main.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async;
+
+import java.io.IOException;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.client.WebTarget;
+
+/**
+ * Long-running asynchronous service client.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class Main {
+
+ /**
+ * Main client entry point.
+ *
+ * @param args command-line arguments.
+ */
+ public static void main(String[] args) {
+ System.exit(runClient(args));
+ }
+
+ /**
+ * Client - business logic.
+ *
+ * @param args command-line arguments.
+ * @return exit code of the utility. {@code 0} if everything completed without errors, {@code -1} otherwise.
+ */
+ static int runClient(final String[] args) {
+ // Parsing command-line arguments
+ final Config config = Config.parse(args);
+ System.out.println(String.format("\nStarting to execute %d requests:\n", config.requests));
+ // Creating JAX-RS client
+ final Client client = ClientBuilder.newClient();
+ // Targeting echo resource at URI "<baseUri>/long-running/(sync|async)/{echo}"
+ final WebTarget echoResource = client.target(config.baseUri).path("long-running/{mode}/{echo}")
+ .resolveTemplate("mode", (config.sync) ? "sync" : "async");
+
+ final CountDownLatch latch = new CountDownLatch(config.requests);
+ final Queue<String> errors = new ConcurrentLinkedQueue<String>();
+ final AtomicInteger requestCounter = new AtomicInteger(0);
+
+ final long tic = System.currentTimeMillis();
+ for (int i = 0; i < config.requests; i++) {
+ final int reqId = i;
+ echoResource.resolveTemplate("echo", reqId).request().async().get(new InvocationCallback<String>() {
+ private final AtomicInteger retries = new AtomicInteger(0);
+
+ @Override
+ public void completed(String response) {
+ final String requestId = Integer.toString(reqId);
+ if (requestId.equals(response)) {
+ System.out.print("*");
+ requestCounter.incrementAndGet();
+ } else {
+ System.out.print("!");
+ errors.offer(String.format("Echo response '%s' not equal to request '%s'", response, requestId));
+ }
+ latch.countDown();
+ }
+
+ @Override
+ public void failed(Throwable error) {
+ if (error.getCause() instanceof IOException && retries.getAndIncrement() < 3) {
+ // resend
+ echoResource.resolveTemplate("echo", reqId).request().async().get(this);
+ } else {
+ System.out.print("!");
+ errors.offer(String.format("Request '%d' has failed: %s", reqId, error.toString()));
+ latch.countDown();
+ }
+ }
+ });
+ }
+
+ try {
+ if (!latch.await(60, TimeUnit.SECONDS)) {
+ errors.offer("Waiting for requests to complete has timed out.");
+ }
+ } catch (InterruptedException e) {
+ errors.offer("Waiting for requests to complete has been interrupted.");
+ }
+ final long toc = System.currentTimeMillis();
+
+ System.out.println(String.format("\n\nExecution finished in %d ms.\nSuccess rate: %6.2f %%",
+ toc - tic,
+ ((double) requestCounter.get() / config.requests) * 100));
+ if (errors.size() > 0) {
+ System.out.println("Following errors occurred during the request execution");
+ for (String error : errors) {
+ System.out.println("\t" + error);
+ }
+ }
+
+ client.close();
+
+ return errors.size() > 0 ? -1 : 0;
+ }
+
+ static class Config {
+
+ /**
+ * Default base URI of the async echo web application.
+ */
+ public static final String DEFAULT_BASE_URI = "http://localhost:8080/server-async-standalone-webapp/";
+
+ final String baseUri;
+ final boolean sync;
+ final int requests;
+
+ Config(String baseUri, boolean sync, int requests) {
+ this.baseUri = baseUri;
+ this.sync = sync;
+ this.requests = requests;
+ }
+
+ public static Config parse(String[] args) {
+ String baseUri = DEFAULT_BASE_URI;
+ boolean sync = false;
+ int requests = 10;
+
+ for (String arg : args) {
+ final String[] keyValuePair = arg.trim().split("=");
+
+ if ("uri".equals(keyValuePair[0])) {
+ baseUri = keyValuePair[1];
+ } else if ("mode".equals(keyValuePair[0])) {
+ sync = "sync".equals(keyValuePair[1]);
+ } else if ("req".equals(keyValuePair[0])) {
+ requests = Integer.parseInt(keyValuePair[1]);
+ } else {
+ System.out.println("WARNING: Unknown parameter: " + keyValuePair[0]);
+ }
+ }
+
+ return new Config(baseUri, sync, requests);
+ }
+
+ @Override
+ public String toString() {
+ return "Config{"
+ + "baseUri='" + baseUri + '\''
+ + ", sync=" + sync
+ + ", requests=" + requests
+ + '}';
+ }
+ }
+}
diff --git a/examples/server-async-standalone/client/src/main/java/org/glassfish/jersey/examples/server/async/MainWindow.form b/examples/server-async-standalone/client/src/main/java/org/glassfish/jersey/examples/server/async/MainWindow.form
new file mode 100644
index 0000000..b78a861
--- /dev/null
+++ b/examples/server-async-standalone/client/src/main/java/org/glassfish/jersey/examples/server/async/MainWindow.form
@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<Form version="1.3" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
+ <NonVisualComponents>
+ <Component class="javax.swing.ButtonGroup" name="syncAsyncSwitchGroup">
+ </Component>
+ </NonVisualComponents>
+ <Properties>
+ <Property name="defaultCloseOperation" type="int" value="3"/>
+ <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[480, 600]"/>
+ </Property>
+ <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[480, 600]"/>
+ </Property>
+ <Property name="resizable" type="boolean" value="false"/>
+ </Properties>
+ <SyntheticProperties>
+ <SyntheticProperty name="formSizePolicy" type="int" value="1"/>
+ </SyntheticProperties>
+ <AuxValues>
+ <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="2"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ </AuxValues>
+
+ <Layout>
+ <DimensionLayout dim="0">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="1" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="1" attributes="0">
+ <Component id="jPanel2" max="32767" attributes="0"/>
+ <Component id="jPanel1" alignment="0" max="32767" attributes="0"/>
+ <Component id="messagePanel" alignment="1" min="-2" pref="468" max="-2" attributes="0"/>
+ </Group>
+ <EmptySpace max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ <DimensionLayout dim="1">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="0" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="jPanel1" min="-2" max="-2" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="jPanel2" min="-2" max="-2" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="messagePanel" pref="456" max="32767" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ </Layout>
+ <SubComponents>
+ <Container class="javax.swing.JPanel" name="jPanel1">
+
+ <Layout>
+ <DimensionLayout dim="0">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Component id="jLabel1" min="-2" max="-2" attributes="0"/>
+ <Component id="jLabel2" alignment="1" min="-2" max="-2" attributes="0"/>
+ </Group>
+ <EmptySpace type="unrelated" max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" attributes="0">
+ <Component id="requestCountField" max="32767" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="syncRadio" min="-2" max="-2" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="asyncRadio" min="-2" max="-2" attributes="0"/>
+ <EmptySpace min="-2" pref="24" max="-2" attributes="0"/>
+ <Component id="runButton" min="-2" max="-2" attributes="0"/>
+ </Group>
+ <Group type="102" attributes="0">
+ <Component id="uriField" max="32767" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ <DimensionLayout dim="1">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="1" attributes="0">
+ <EmptySpace max="32767" attributes="0"/>
+ <Group type="103" groupAlignment="3" attributes="0">
+ <Component id="uriField" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="jLabel2" alignment="3" min="-2" max="-2" attributes="0"/>
+ </Group>
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="3" attributes="0">
+ <Component id="requestCountField" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="jLabel1" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="syncRadio" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="asyncRadio" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="runButton" alignment="3" min="-2" max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="jLabel1">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Number of requests"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JTextField" name="requestCountField">
+ <Properties>
+ <Property name="horizontalAlignment" type="int" value="4"/>
+ <Property name="text" type="java.lang.String" value="10"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JRadioButton" name="syncRadio">
+ <Properties>
+ <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
+ <ComponentRef name="syncAsyncSwitchGroup"/>
+ </Property>
+ <Property name="text" type="java.lang.String" value="sync"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JRadioButton" name="asyncRadio">
+ <Properties>
+ <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
+ <ComponentRef name="syncAsyncSwitchGroup"/>
+ </Property>
+ <Property name="selected" type="boolean" value="true"/>
+ <Property name="text" type="java.lang.String" value="async"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JTextField" name="uriField">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="jTextField2"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JLabel" name="jLabel2">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Base web app URL"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JButton" name="runButton">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Run"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="runButtonActionPerformed"/>
+ </Events>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="messagePanel">
+ <Properties>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
+ <EtchetBorder/>
+ </Border>
+ </Property>
+ </Properties>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="jLabel3">
+ <Properties>
+ <Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
+ <Color blue="0" green="ff" red="ff" type="rgb"/>
+ </Property>
+ <Property name="text" type="java.lang.String" value=" "/>
+ <Property name="toolTipText" type="java.lang.String" value="Running..."/>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.LineBorderInfo">
+ <LineBorder/>
+ </Border>
+ </Property>
+ <Property name="opaque" type="boolean" value="true"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JLabel" name="jLabel4">
+ <Properties>
+ <Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
+ <Color blue="0" green="ff" red="ff" type="rgb"/>
+ </Property>
+ <Property name="text" type="java.lang.String" value=" "/>
+ <Property name="toolTipText" type="java.lang.String" value="Running..."/>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.LineBorderInfo">
+ <LineBorder/>
+ </Border>
+ </Property>
+ <Property name="opaque" type="boolean" value="true"/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="jPanel2">
+
+ <Layout>
+ <DimensionLayout dim="0">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Component id="finishStatusLabel" max="32767" attributes="0"/>
+ <Component id="successRateStatusLabel" max="32767" attributes="0"/>
+ </Group>
+ <EmptySpace max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ <DimensionLayout dim="1">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="0" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="finishStatusLabel" min="-2" max="-2" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="successRateStatusLabel" min="-2" max="-2" attributes="0"/>
+ <EmptySpace max="32767" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="finishStatusLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" value=" "/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JLabel" name="successRateStatusLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" value=" "/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/examples/server-async-standalone/client/src/main/java/org/glassfish/jersey/examples/server/async/MainWindow.java b/examples/server-async-standalone/client/src/main/java/org/glassfish/jersey/examples/server/async/MainWindow.java
new file mode 100644
index 0000000..de9df4b
--- /dev/null
+++ b/examples/server-async-standalone/client/src/main/java/org/glassfish/jersey/examples/server/async/MainWindow.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async;
+
+import java.awt.Color;
+import java.awt.EventQueue;
+import java.awt.FlowLayout;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.client.WebTarget;
+
+import javax.swing.JLabel;
+
+/**
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class MainWindow extends javax.swing.JFrame {
+
+ /**
+ * Creates new form MainWindow
+ */
+ public MainWindow() {
+ initComponents();
+
+ uriField.setText(Main.Config.DEFAULT_BASE_URI);
+ ((FlowLayout) messagePanel.getLayout()).setAlignment(FlowLayout.LEADING);
+ messagePanel.removeAll();
+ messagePanel.revalidate();
+ }
+
+ /**
+ * This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ syncAsyncSwitchGroup = new javax.swing.ButtonGroup();
+ jPanel1 = new javax.swing.JPanel();
+ jLabel1 = new javax.swing.JLabel();
+ requestCountField = new javax.swing.JTextField();
+ syncRadio = new javax.swing.JRadioButton();
+ asyncRadio = new javax.swing.JRadioButton();
+ uriField = new javax.swing.JTextField();
+ jLabel2 = new javax.swing.JLabel();
+ runButton = new javax.swing.JButton();
+ messagePanel = new javax.swing.JPanel();
+ jLabel3 = new javax.swing.JLabel();
+ jLabel4 = new javax.swing.JLabel();
+ jPanel2 = new javax.swing.JPanel();
+ finishStatusLabel = new javax.swing.JLabel();
+ successRateStatusLabel = new javax.swing.JLabel();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
+ setMaximumSize(new java.awt.Dimension(480, 600));
+ setMinimumSize(new java.awt.Dimension(480, 600));
+ setResizable(false);
+
+ jLabel1.setText("Number of requests");
+
+ requestCountField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
+ requestCountField.setText("10");
+
+ syncAsyncSwitchGroup.add(syncRadio);
+ syncRadio.setText("sync");
+
+ syncAsyncSwitchGroup.add(asyncRadio);
+ asyncRadio.setSelected(true);
+ asyncRadio.setText("async");
+
+ uriField.setText("jTextField2");
+
+ jLabel2.setText("Base web app URL");
+
+ runButton.setText("Run");
+ runButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ runButtonActionPerformed(evt);
+ }
+ });
+
+ org.jdesktop.layout.GroupLayout jPanel1Layout = new org.jdesktop.layout.GroupLayout(jPanel1);
+ jPanel1.setLayout(jPanel1Layout);
+ jPanel1Layout.setHorizontalGroup(
+ jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel1Layout.createSequentialGroup()
+ .addContainerGap()
+ .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jLabel1)
+ .add(org.jdesktop.layout.GroupLayout.TRAILING, jLabel2))
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
+ .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel1Layout.createSequentialGroup()
+ .add(requestCountField)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(syncRadio)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(asyncRadio)
+ .add(24, 24, 24)
+ .add(runButton))
+ .add(jPanel1Layout.createSequentialGroup()
+ .add(uriField)
+ .addContainerGap())))
+ );
+ jPanel1Layout.setVerticalGroup(
+ jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(org.jdesktop.layout.GroupLayout.TRAILING, jPanel1Layout.createSequentialGroup()
+ .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
+ .add(uriField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
+ .add(jLabel2))
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
+ .add(requestCountField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
+ .add(jLabel1)
+ .add(syncRadio)
+ .add(asyncRadio)
+ .add(runButton)))
+ );
+
+ messagePanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
+
+ jLabel3.setBackground(new java.awt.Color(255, 255, 0));
+ jLabel3.setText(" ");
+ jLabel3.setToolTipText("Running...");
+ jLabel3.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
+ jLabel3.setOpaque(true);
+ messagePanel.add(jLabel3);
+
+ jLabel4.setBackground(new java.awt.Color(255, 255, 0));
+ jLabel4.setText(" ");
+ jLabel4.setToolTipText("Running...");
+ jLabel4.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
+ jLabel4.setOpaque(true);
+ messagePanel.add(jLabel4);
+
+ finishStatusLabel.setText(" ");
+
+ successRateStatusLabel.setText(" ");
+
+ org.jdesktop.layout.GroupLayout jPanel2Layout = new org.jdesktop.layout.GroupLayout(jPanel2);
+ jPanel2.setLayout(jPanel2Layout);
+ jPanel2Layout.setHorizontalGroup(
+ jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel2Layout.createSequentialGroup()
+ .addContainerGap()
+ .add(jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(finishStatusLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .add(successRateStatusLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addContainerGap())
+ );
+ jPanel2Layout.setVerticalGroup(
+ jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel2Layout.createSequentialGroup()
+ .addContainerGap()
+ .add(finishStatusLabel)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(successRateStatusLabel)
+ .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+
+ org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
+ .addContainerGap()
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
+ .add(jPanel2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .add(org.jdesktop.layout.GroupLayout.LEADING, jPanel1,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .add(messagePanel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 468,
+ org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(layout.createSequentialGroup()
+ .addContainerGap()
+ .add(jPanel1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(jPanel2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(messagePanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 456, Short.MAX_VALUE)
+ .addContainerGap())
+ );
+
+ pack();
+ }// </editor-fold>//GEN-END:initComponents
+
+ private void runButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_runButtonActionPerformed
+ final Main.Config config = new Main.Config(uriField.getText(), syncRadio.isSelected(),
+ Integer.parseInt(requestCountField.getText()));
+ runButton.setEnabled(false);
+ finishStatusLabel.setText(" ");
+ successRateStatusLabel.setText(" ");
+ messagePanel.removeAll();
+ messagePanel.revalidate();
+ messagePanel.repaint();
+ Executors.newSingleThreadExecutor().submit(new Runnable() {
+
+ @Override
+ public void run() {
+ sendMessages(config);
+ }
+ });
+ }//GEN-LAST:event_runButtonActionPerformed
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String args[]) {
+ /* Set the Nimbus look and feel */
+ //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
+ /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
+ * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
+ */
+// try {
+// for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
+// if ("Nimbus".equals(info.getName())) {
+// javax.swing.UIManager.setLookAndFeel(info.getClassName());
+// break;
+// }
+// }
+// } catch (ClassNotFoundException ex) {
+// java.util.logging.Logger.getLogger(MainWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
+// } catch (InstantiationException ex) {
+// java.util.logging.Logger.getLogger(MainWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
+// } catch (IllegalAccessException ex) {
+// java.util.logging.Logger.getLogger(MainWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
+// } catch (javax.swing.UnsupportedLookAndFeelException ex) {
+// java.util.logging.Logger.getLogger(MainWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
+// }
+ //</editor-fold>
+
+ /* Create and display the form */
+ java.awt.EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ final MainWindow mainWindow = new MainWindow();
+ mainWindow.setLocationRelativeTo(null);
+ mainWindow.setVisible(true);
+ }
+ });
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JRadioButton asyncRadio;
+ private javax.swing.JLabel finishStatusLabel;
+ private javax.swing.JLabel jLabel1;
+ private javax.swing.JLabel jLabel2;
+ private javax.swing.JLabel jLabel3;
+ private javax.swing.JLabel jLabel4;
+ private javax.swing.JPanel jPanel1;
+ private javax.swing.JPanel jPanel2;
+ private javax.swing.JPanel messagePanel;
+ private javax.swing.JTextField requestCountField;
+ private javax.swing.JButton runButton;
+ private javax.swing.JLabel successRateStatusLabel;
+ private javax.swing.ButtonGroup syncAsyncSwitchGroup;
+ private javax.swing.JRadioButton syncRadio;
+ private javax.swing.JTextField uriField;
+ // End of variables declaration//GEN-END:variables
+
+ private final Color PROGRESS_COLOR = new Color(255, 255, 0);
+ private final Color ERROR_COLOR = new Color(255, 0, 0);
+ private final Color SUCCESS_COLOR = new Color(0, 255, 0);
+
+ private JLabel createRequestStatusLabel() {
+ final JLabel label = new JLabel(" ");
+ invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+
+ label.setBackground(PROGRESS_COLOR);
+ label.setToolTipText("Running...");
+ label.setBorder(javax.swing.BorderFactory.createLineBorder(new Color(0, 0, 0)));
+ label.setOpaque(true);
+
+ messagePanel.add(label);
+ messagePanel.revalidate();
+ }
+ });
+
+ return label;
+ }
+
+ private void invokeAndWait(Runnable event) {
+ try {
+ EventQueue.invokeAndWait(event);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void sendMessages(final Main.Config config) {
+ // Creating JAX-RS client
+ final Client client = ClientBuilder.newClient();
+ // Targeting echo resource at URI "<baseUri>/long-running/(sync|async)/{echo}"
+ final WebTarget echoResource = client.target(config.baseUri).path("long-running/{mode}/{echo}")
+ .resolveTemplate("mode", (config.sync) ? "sync" : "async");
+
+ final CountDownLatch latch = new CountDownLatch(config.requests);
+ final AtomicInteger requestCounter = new AtomicInteger(0);
+
+ final long tic = System.currentTimeMillis();
+ for (int i = 0; i < config.requests; i++) {
+ final int reqId = i;
+
+ final JLabel requestStatusLabel = createRequestStatusLabel();
+
+ echoResource.resolveTemplate("echo", reqId).request().async().get(new InvocationCallback<String>() {
+ private final AtomicInteger retries = new AtomicInteger(0);
+
+ @Override
+ public void completed(final String response) {
+ invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ final String requestId = Integer.toString(reqId);
+ if (requestId.equals(response)) {
+ requestStatusLabel.setBackground(SUCCESS_COLOR);
+ requestStatusLabel.setToolTipText(requestId);
+ requestCounter.incrementAndGet();
+ } else {
+ requestStatusLabel.setBackground(ERROR_COLOR);
+ requestStatusLabel.setToolTipText(String.format("Echo response '%s' not equal to request '%s'",
+ response, requestId));
+ }
+ latch.countDown();
+ }
+ });
+ }
+
+ @Override
+ public void failed(final Throwable error) {
+ if (error.getCause() instanceof IOException && retries.getAndIncrement() < 3) {
+ // resend
+ echoResource.resolveTemplate("echo", reqId).request().async().get(this);
+ } else {
+ invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ requestStatusLabel.setBackground(ERROR_COLOR);
+ requestStatusLabel.setToolTipText(String.format("Request '%d' has failed: %s",
+ reqId, error.toString()));
+ latch.countDown();
+ }
+ });
+ }
+ }
+ });
+ }
+
+ try {
+ if (!latch.await(60, TimeUnit.SECONDS)) {
+ System.out.println("Waiting for requests to complete has timed out.");
+ }
+ } catch (InterruptedException e) {
+ System.out.println("Waiting for requests to complete has been interrupted.");
+ }
+ final long toc = System.currentTimeMillis();
+
+ invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ finishStatusLabel.setText(String.format("Execution finished in %d ms.", toc - tic));
+ successRateStatusLabel.setText(String.format("Success rate: %6.2f %%",
+ ((double) requestCounter.get() / config.requests) * 100));
+ }
+ });
+ client.close();
+
+ EventQueue.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ runButton.setEnabled(true);
+ }
+ });
+ }
+}
diff --git a/examples/server-async-standalone/client/src/test/java/org/glassfish/jersey/examples/server/async/MainTest.java b/examples/server-async-standalone/client/src/test/java/org/glassfish/jersey/examples/server/async/MainTest.java
new file mode 100644
index 0000000..2826e44
--- /dev/null
+++ b/examples/server-async-standalone/client/src/test/java/org/glassfish/jersey/examples/server/async/MainTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test whether cases described in {@code README} are passing.
+ *
+ * @author Michal Gajdos
+ */
+public class MainTest {
+
+ @Test
+ public void testSync() throws Exception {
+ assertEquals(0, Main.runClient(new String[]{"req=10", "mode=sync"}));
+ }
+
+ @Test
+ public void testAsync() throws Exception {
+ assertEquals(0, Main.runClient(new String[]{"req=10", "mode=async"}));
+ }
+
+ @Test
+ public void testAsyncWrongUri() throws Exception {
+ assertEquals(-1, Main.runClient(new String[]{"req=1", "mode=async", "uri=http://foo.bar"}));
+ }
+
+ @Test
+ public void testSyncWrongUri() throws Exception {
+ assertEquals(-1, Main.runClient(new String[]{"req=1", "mode=sync", "uri=http://foo.bar"}));
+ }
+}
diff --git a/examples/server-async-standalone/pom.xml b/examples/server-async-standalone/pom.xml
new file mode 100644
index 0000000..04c3e8a
--- /dev/null
+++ b/examples/server-async-standalone/pom.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>server-async-standalone</artifactId>
+ <packaging>pom</packaging>
+ <name>jersey-examples-server-async-standalone</name>
+
+ <description>Standalone Jersey JAX-RS asynchronous server-side processing example.</description>
+
+ <modules>
+ <module>client</module>
+ <module>webapp</module>
+ </modules>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/server-async-standalone/webapp/pom.xml b/examples/server-async-standalone/webapp/pom.xml
new file mode 100644
index 0000000..22bc1b8
--- /dev/null
+++ b/examples/server-async-standalone/webapp/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>server-async-standalone</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>server-async-standalone-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-server-async-standalone-webapp</name>
+
+ <description>Standalone Jersey JAX-RS asynchronous server-side processing example web application.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <!-- do not create source zip bundles -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <skipAssembly>true</skipAssembly>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/server-async-standalone/webapp/src/main/java/org/glassfish/jersey/examples/server/async/AsyncJaxrsApplication.java b/examples/server-async-standalone/webapp/src/main/java/org/glassfish/jersey/examples/server/async/AsyncJaxrsApplication.java
new file mode 100644
index 0000000..505f327
--- /dev/null
+++ b/examples/server-async-standalone/webapp/src/main/java/org/glassfish/jersey/examples/server/async/AsyncJaxrsApplication.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+
+/**
+ * Jersey Async Webapp application class.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@ApplicationPath("/")
+public class AsyncJaxrsApplication extends Application {
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ final HashSet<Class<?>> classes = new HashSet<Class<?>>();
+ classes.add(LongRunningEchoResource.class);
+
+ return classes;
+ }
+
+ @Override
+ public Set<Object> getSingletons() {
+ final HashSet<Object> instances = new HashSet<Object>();
+
+ instances.add(new LoggingFeature(Logger.getLogger(AsyncJaxrsApplication.class.getName()),
+ LoggingFeature.Verbosity.PAYLOAD_ANY));
+
+ return instances;
+ }
+}
diff --git a/examples/server-async-standalone/webapp/src/main/java/org/glassfish/jersey/examples/server/async/LongRunningEchoResource.java b/examples/server-async-standalone/webapp/src/main/java/org/glassfish/jersey/examples/server/async/LongRunningEchoResource.java
new file mode 100644
index 0000000..f6d016b
--- /dev/null
+++ b/examples/server-async-standalone/webapp/src/main/java/org/glassfish/jersey/examples/server/async/LongRunningEchoResource.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.ServiceUnavailableException;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+
+/**
+ * Example of a simple resource with a long-running operation executed in a
+ * custom application thread.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("long-running")
+@Produces("text/plain")
+public class LongRunningEchoResource {
+
+ private static final int SLEEP_TIME_IN_MILLIS = 1000;
+ private static final ExecutorService TASK_EXECUTOR = Executors.newCachedThreadPool();
+
+ /**
+ * Synchronously echo the last path segment after sleeping for a long time.
+ *
+ * @param echo message to echo.
+ * @return echoed message.
+ */
+ @GET
+ @Path("sync/{echo}")
+ public String syncEcho(@PathParam("echo") final String echo) {
+ try {
+ Thread.sleep(SLEEP_TIME_IN_MILLIS);
+ } catch (final InterruptedException ex) {
+ throw new ServiceUnavailableException();
+ }
+ return echo;
+ }
+
+ /**
+ * Asynchronously echo the last path segment after sleeping for a long time.
+ *
+ * @param echo message to echo.
+ * @param ar AsynchronousResponse, will contain echoed message.
+ */
+ @GET
+ @Path("async/{echo}")
+ public void asyncEcho(@PathParam("echo") final String echo, @Suspended final AsyncResponse ar) {
+ TASK_EXECUTOR.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(SLEEP_TIME_IN_MILLIS);
+ } catch (final InterruptedException ex) {
+ ar.cancel();
+ }
+ ar.resume(echo);
+ }
+ });
+ }
+}
diff --git a/examples/server-async-standalone/webapp/src/main/webapp/WEB-INF/web.xml b/examples/server-async-standalone/webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..5f63815
--- /dev/null
+++ b/examples/server-async-standalone/webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+ <servlet>
+ <servlet-name>async-app</servlet-name>
+ <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.glassfish.jersey.examples.server.async.AsyncJaxrsApplication</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ <async-supported>true</async-supported>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>async-app</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+</web-app>
diff --git a/examples/server-async/README.MD b/examples/server-async/README.MD
new file mode 100644
index 0000000..1b4d180
--- /dev/null
+++ b/examples/server-async/README.MD
@@ -0,0 +1,80 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Server Asynchronous Example
+===========================
+
+The full description how to create an asynchronous resource can be found in Jersey User Guide, chapter
+[Asynchronous Services and Clients](https://jersey.java.net/documentation/latest/async.html).
+
+This example demonstrates JAX-RS 2.0 server-side non-blocking API in comparison to blocking and long-running
+asynchronous operations.
+
+The goal of the sample is to demonstrate that with limited I/O processing threads
+on the server the synchronous execution of a long-running task may lead to resource
+starvation caused by I/O processing threads being blocked by the long-running
+operation, unable to serve new incoming requests.
+
+OTOH, when the same long-running operation is executed asynchronously, the I/O
+threads do not need to block while waiting for the long-running operation to finish
+and the overall throughput of the system is greatly increased.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+------------------------------------ | --------------------------| --------------
+**_async/messaging/fireAndForget_** | FireAndForgetChatResource | POST1
+**_async/messaging/fireAndForget_** | FireAndForgetChatResource | GET
+**_async/messaging/blocking_** | BlockingPostChatResource | POST
+**_async/messaging/blocking_** | BlockingPostChatResource | GET
+**_async/longrunning_** | SimpleLongRunningResource | GET
+
+Sample Response
+---------------
+
+This section shortly describes a main difference between blocking and non-blocking approach implemented
+in the example.
+
+#### Blocking Approach
+
+First you have to sent POST request which is shown below and repeat this call 5-times.
+
+> curl -v -X POST http://localhost:8080/base/async/messaging/blocking -H "Content-Type: text/plain" -d "My message"
+
+All previous requests were processed using I/O Container threads, there was no problem with
+a returning response and a response's delay until now. But if you run the 6-th call, the server will
+be blocked. At this time I/O thread is blocked (because of ArrayBlockingQueue is full) and waits for
+the call which should read and remove one of the saved messages. Once the a below mentioned
+request is called, the Blocking has place for another message and waiting thread is processed.
+
+> curl -v -X GET http://localhost:8080/base/async/messaging/blocking
+
+#### Non-blocking Approach
+
+We can start with the same procedure as in previous approach. Sent the POST request shown below 5-times
+
+> curl -v -X POST http://localhost:8080/base/async/messaging/fireAndForget -H "Content-Type: text/plain" -d "My message"
+
+At this time blocking queue in resource should be full, but if you send another request, the client application won't
+be blocked and will receive the given response immediately. Since the server application doesn't block the I/O thread
+but blocks a thread which is dedicated for processing the request. After the GET call, the blocked dedicated thread
+is processed and returned to a cached thread pool.
+
+> curl -v -X GET http://localhost:8080/base/async/messaging/fireAndForget
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
diff --git a/examples/server-async/pom.xml b/examples/server-async/pom.xml
new file mode 100644
index 0000000..90d0c23
--- /dev/null
+++ b/examples/server-async/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>server-async</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-server-async</name>
+
+ <description>Jersey JAX-RS asynchronous server-side example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.server.async.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/App.java b/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/App.java
new file mode 100644
index 0000000..12063f2
--- /dev/null
+++ b/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/App.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import static org.glassfish.jersey.logging.LoggingFeature.Verbosity.PAYLOAD_ANY;
+
+/**
+ * Jersey example application for custom executors managed async resources.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/base/");
+ public static final String ASYNC_MESSAGING_FIRE_N_FORGET_PATH = "async/messaging/fireAndForget";
+ public static final String ASYNC_MESSAGING_BLOCKING_PATH = "async/messaging/blocking";
+ public static final String ASYNC_LONG_RUNNING_OP_PATH = "async/longrunning";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Async resources\" Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, create(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format(
+ "Application started.\n"
+ + "To test simple, non-blocking asynchronous messaging resource, try %s%s\n"
+ + "To test blocking version of asynchronous messaging resource, try %s%s\n"
+ + "To test long-running asynchronous operation resource, try %s%s\n"
+ + "Stop the application using CTRL+C",
+ BASE_URI, ASYNC_MESSAGING_FIRE_N_FORGET_PATH,
+ BASE_URI, ASYNC_MESSAGING_BLOCKING_PATH,
+ BASE_URI, ASYNC_LONG_RUNNING_OP_PATH));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ public static ResourceConfig create() {
+ final ResourceConfig resourceConfig = new ResourceConfig()
+ .registerClasses(BlockingPostChatResource.class, FireAndForgetChatResource.class, SimpleLongRunningResource.class)
+ .registerInstances(new LoggingFeature(Logger.getLogger(App.class.getName()), PAYLOAD_ANY));
+
+ return resourceConfig;
+ }
+}
diff --git a/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/BlockingPostChatResource.java b/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/BlockingPostChatResource.java
new file mode 100644
index 0000000..84b8348
--- /dev/null
+++ b/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/BlockingPostChatResource.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+
+/**
+ * Example of a simple blocking point-to-point messaging resource.
+ *
+ * This version of the messaging resource blocks when POSTing a new message until
+ * the message is retrieved.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path(App.ASYNC_MESSAGING_BLOCKING_PATH)
+@Produces(MediaType.TEXT_PLAIN)
+@Consumes(MediaType.TEXT_PLAIN)
+public class BlockingPostChatResource {
+
+ public static final String POST_NOTIFICATION_RESPONSE = "Message stored.";
+ //
+ private static final Logger LOGGER = Logger.getLogger(BlockingPostChatResource.class.getName());
+ private static final Level DEBUG = Level.INFO;
+ //
+ private static final ExecutorService QUEUE_EXECUTOR = Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+ .setNameFormat("blocking-post-chat-resource-executor-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+ private static final BlockingQueue<String> messages = new ArrayBlockingQueue<String>(5);
+ //
+
+ @GET
+ public void pickUpMessage(@Suspended final AsyncResponse ar, @QueryParam("id") final String messageId) {
+ LOGGER.log(DEBUG, "Received GET <{0}> with context {1} on thread {2}.",
+ new Object[]{messageId, ar.toString(), Thread.currentThread().getName()});
+ QUEUE_EXECUTOR.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ final String message = messages.take();
+ LOGGER.log(DEBUG, "Resuming GET <{0}> context {1} with a message {2} on thread {3}.",
+ new Object[]{messageId, ar.toString(), message, Thread.currentThread().getName()});
+ ar.resume(message);
+ } catch (InterruptedException ex) {
+ LOGGER.log(Level.SEVERE,
+ "Waiting for a message pick-up interrupted. Cancelling context" + ar.toString(), ex);
+ ar.cancel(); // close the open connection
+ }
+ }
+ });
+ }
+
+ @POST
+ public void postMessage(@Suspended final AsyncResponse ar, final String message) {
+ LOGGER.log(DEBUG, "Received POST <{0}> with context {1} on thread {2}. Suspending the context.",
+ new Object[]{message, ar.toString(), Thread.currentThread().getName()});
+ QUEUE_EXECUTOR.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ messages.put(message);
+ LOGGER.log(DEBUG, "POSTed message <{0}> successfully queued. Resuming POST with context {1} on thread {2}.",
+ new Object[]{message, ar.toString(), Thread.currentThread().getName()});
+ ar.resume(POST_NOTIFICATION_RESPONSE);
+ } catch (InterruptedException ex) {
+ LOGGER.log(Level.SEVERE,
+ "Waiting for a queueing a message '" + message + "' has been interrupted.", ex);
+ ar.resume(ex); // propagate info about the problem
+ }
+ }
+ });
+ }
+}
diff --git a/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/FireAndForgetChatResource.java b/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/FireAndForgetChatResource.java
new file mode 100644
index 0000000..7a32d39
--- /dev/null
+++ b/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/FireAndForgetChatResource.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+
+/**
+ * Example of a simple fire&forget point-to-point messaging resource.
+ *
+ * This version of the messaging resource does not block when POSTing a new message.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path(App.ASYNC_MESSAGING_FIRE_N_FORGET_PATH)
+@Produces(MediaType.TEXT_PLAIN)
+@Consumes(MediaType.TEXT_PLAIN)
+public class FireAndForgetChatResource {
+
+ public static final String POST_NOTIFICATION_RESPONSE = "Message sent";
+ //
+ private static final Logger LOGGER = Logger.getLogger(FireAndForgetChatResource.class.getName());
+ private static final Level DEBUG = Level.INFO;
+ //
+ private static final ExecutorService QUEUE_EXECUTOR = Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+ .setNameFormat("fire&forget-chat-resource-executor-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+ private static final BlockingQueue<AsyncResponse> suspended = new ArrayBlockingQueue<AsyncResponse>(5);
+
+ @GET
+ public void pickUpMessage(@Suspended final AsyncResponse ar, @QueryParam("id") final String messageId)
+ throws InterruptedException {
+ LOGGER.log(DEBUG, "Received GET <{0}> with context {1} on thread {2}.",
+ new Object[] {messageId, ar.toString(), Thread.currentThread().getName()});
+ QUEUE_EXECUTOR.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ suspended.put(ar);
+ LOGGER.log(DEBUG, "GET <{0}> context {1} scheduled for resume.",
+ new Object[] {messageId, ar.toString()});
+ } catch (InterruptedException ex) {
+ LOGGER.log(Level.SEVERE,
+ "Waiting for a message pick-up interrupted. Cancelling context" + ar.toString(), ex);
+ ar.cancel(); // close the open connection
+ }
+ }
+ });
+ }
+
+ @POST
+ public String postMessage(final String message) throws InterruptedException {
+ LOGGER.log(DEBUG, "Received POSTed message <{0}> on thread {1}.",
+ new Object[] {message, Thread.currentThread().getName()});
+ QUEUE_EXECUTOR.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ final AsyncResponse suspendedResponse = suspended.take();
+ LOGGER.log(DEBUG, "Resuming GET context {0} with a message <{1}> on thread {2}.",
+ new Object[] {suspendedResponse.toString(), message, Thread.currentThread().getName()});
+ suspendedResponse.resume(message);
+ } catch (InterruptedException ex) {
+ LOGGER.log(Level.SEVERE,
+ "Waiting for a sending a message <" + message + "> has been interrupted.", ex);
+ }
+ }
+ });
+
+ return POST_NOTIFICATION_RESPONSE;
+ }
+}
diff --git a/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/LongRunningAsyncOperationResource.java b/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/LongRunningAsyncOperationResource.java
new file mode 100644
index 0000000..15b3a08
--- /dev/null
+++ b/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/LongRunningAsyncOperationResource.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+
+/**
+ * Example resource for long running async operations.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+//TODO move the test to integration tests.
+//@Path(App.LONG_RUNNING_ASYNC_OP_PATH)
+@Produces("text/plain")
+public class LongRunningAsyncOperationResource {
+
+ public static final String NOTIFICATION_RESPONSE = "Hello async world!";
+ //
+ private static final Logger LOGGER = Logger.getLogger(LongRunningAsyncOperationResource.class.getName());
+ private static final int SLEEP_TIME_IN_MILLIS = 1000;
+ private static final ExecutorService TASK_EXECUTOR = Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+ .setNameFormat("long-running-resource-executor-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+
+ @GET
+ @Path("basicSyncExample")
+ public String basicSyncExample() {
+ try {
+ Thread.sleep(SLEEP_TIME_IN_MILLIS);
+ } catch (InterruptedException ex) {
+ LOGGER.log(Level.SEVERE, "Response processing interrupted", ex);
+ }
+ return NOTIFICATION_RESPONSE;
+ }
+
+ @GET
+ @Path("async")
+ public void suspendViaContextExample(@Suspended final AsyncResponse ar, @QueryParam("query") final String query) {
+ TASK_EXECUTOR.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(SLEEP_TIME_IN_MILLIS);
+ } catch (InterruptedException ex) {
+ LOGGER.log(Level.SEVERE, "Response processing interrupted", ex);
+ }
+ ar.resume("Complex result for " + query);
+ }
+ });
+ }
+}
diff --git a/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/SimpleLongRunningResource.java b/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/SimpleLongRunningResource.java
new file mode 100644
index 0000000..76cae73
--- /dev/null
+++ b/examples/server-async/src/main/java/org/glassfish/jersey/examples/server/async/SimpleLongRunningResource.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+
+/**
+ * Example of a simple resource with a long-running operation executed in a
+ * custom application thread.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path(App.ASYNC_LONG_RUNNING_OP_PATH)
+@Produces("text/plain")
+public class SimpleLongRunningResource {
+
+ public static final String NOTIFICATION_RESPONSE = "Hello async world!";
+ private static final Logger LOGGER = Logger.getLogger(SimpleLongRunningResource.class.getName());
+ private static final int SLEEP_TIME_IN_MILLIS = 1000;
+ private static final ExecutorService TASK_EXECUTOR = Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+ .setNameFormat("long-running-resource-executor-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+
+ @GET
+ public void longGet(@Suspended final AsyncResponse ar) {
+ TASK_EXECUTOR.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(SLEEP_TIME_IN_MILLIS);
+ } catch (InterruptedException ex) {
+ LOGGER.log(Level.SEVERE, "Response processing interrupted", ex);
+ }
+ ar.resume(NOTIFICATION_RESPONSE);
+ }
+ });
+ }
+}
diff --git a/examples/server-async/src/test/java/org/glassfish/jersey/examples/server/async/AsyncResourceTest.java b/examples/server-async/src/test/java/org/glassfish/jersey/examples/server/async/AsyncResourceTest.java
new file mode 100644
index 0000000..1e7c8dc
--- /dev/null
+++ b/examples/server-async/src/test/java/org/glassfish/jersey/examples/server/async/AsyncResourceTest.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.server.async;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class AsyncResourceTest extends JerseyTest {
+
+ private static final Logger LOGGER = Logger.getLogger(AsyncResourceTest.class.getName());
+
+ @Override
+ protected ResourceConfig configure() {
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+ enable(TestProperties.LOG_TRAFFIC);
+ enable(TestProperties.DUMP_ENTITY);
+ return App.create();
+ }
+
+ @Test
+ public void testFireAndForgetChatResource() throws InterruptedException {
+ executeChatTest(target().path(App.ASYNC_MESSAGING_FIRE_N_FORGET_PATH),
+ FireAndForgetChatResource.POST_NOTIFICATION_RESPONSE);
+ }
+
+ @Test
+ public void testBlockingPostChatResource() throws InterruptedException {
+ executeChatTest(target().path(App.ASYNC_MESSAGING_BLOCKING_PATH), BlockingPostChatResource.POST_NOTIFICATION_RESPONSE);
+ }
+
+ private void executeChatTest(final WebTarget resourceTarget, final String expectedPostResponse) throws InterruptedException {
+ final int MAX_MESSAGES = 100;
+ final int LATCH_WAIT_TIMEOUT = 10 * getAsyncTimeoutMultiplier();
+ final boolean debugMode = false;
+ final boolean sequentialGet = false;
+ final boolean sequentialPost = false;
+ final Object sequentialGetLock = new Object();
+ final Object sequentialPostLock = new Object();
+
+ final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+ .setNameFormat("async-resource-test-%02d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+
+ final Map<Integer, String> postResponses = new ConcurrentHashMap<Integer, String>();
+ final Map<Integer, String> getResponses = new ConcurrentHashMap<Integer, String>();
+
+ final CountDownLatch postRequestLatch = new CountDownLatch(MAX_MESSAGES);
+ final CountDownLatch getRequestLatch = new CountDownLatch(MAX_MESSAGES);
+
+ try {
+ for (int i = 0; i < MAX_MESSAGES; i++) {
+ final int requestId = i;
+ executor.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ if (debugMode || sequentialPost) {
+ synchronized (sequentialPostLock) {
+ post();
+ }
+ } else {
+ post();
+ }
+ }
+
+ private void post() {
+ try {
+ int attemptCounter = 0;
+ while (true) {
+ attemptCounter++;
+ try {
+ final String response = resourceTarget.request()
+ .post(Entity.text(String.format("%02d", requestId)), String.class);
+ postResponses.put(requestId, response);
+ break;
+ } catch (Throwable t) {
+ LOGGER.log(Level.WARNING, String.format("Error POSTING message <%s> for %d. time.",
+ requestId, attemptCounter), t);
+ }
+ if (attemptCounter > 3) {
+ break;
+ }
+ Thread.sleep(10);
+ }
+ } catch (InterruptedException ignored) {
+ LOGGER.log(Level.WARNING,
+ String.format("Error POSTING message <%s>: Interrupted", requestId), ignored);
+ } finally {
+ postRequestLatch.countDown();
+ }
+ }
+ });
+ executor.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ if (debugMode || sequentialGet) {
+ synchronized (sequentialGetLock) {
+ get();
+ }
+ } else {
+ get();
+ }
+ }
+
+ private void get() {
+ try {
+ int attemptCounter = 0;
+ while (true) {
+ attemptCounter++;
+ try {
+ final String response = resourceTarget.queryParam("id", requestId).request()
+ .get(String.class);
+ getResponses.put(requestId, response);
+ break;
+ } catch (Throwable t) {
+ LOGGER.log(Level.SEVERE, String.format("Error sending GET request <%s> for %d. time.",
+ requestId, attemptCounter), t);
+ }
+ if (attemptCounter > 3) {
+ break;
+ }
+ Thread.sleep(10);
+ }
+ } catch (InterruptedException ignored) {
+ LOGGER.log(Level.WARNING,
+ String.format("Error sending GET message <%s>: Interrupted", requestId), ignored);
+ } finally {
+ getRequestLatch.countDown();
+ }
+ }
+ });
+ }
+
+ if (debugMode) {
+ postRequestLatch.await();
+ getRequestLatch.await();
+ } else {
+ if (!postRequestLatch.await(LATCH_WAIT_TIMEOUT, TimeUnit.SECONDS)) {
+ LOGGER.log(Level.SEVERE, "Waiting for all POST requests to complete has timed out.");
+ }
+ if (!getRequestLatch.await(LATCH_WAIT_TIMEOUT, TimeUnit.SECONDS)) {
+ LOGGER.log(Level.SEVERE, "Waiting for all GET requests to complete has timed out.");
+ }
+ }
+ } finally {
+ executor.shutdownNow();
+ }
+
+ StringBuilder messageBuilder = new StringBuilder("POST responses received: ").append(postResponses.size()).append("\n");
+ for (Map.Entry<Integer, String> postResponseEntry : postResponses.entrySet()) {
+ messageBuilder.append(String.format("POST response for message %02d: ", postResponseEntry.getKey()))
+ .append(postResponseEntry.getValue()).append('\n');
+ }
+ messageBuilder.append('\n');
+ messageBuilder.append("GET responses received: ").append(getResponses.size()).append("\n");
+ for (Map.Entry<Integer, String> getResponseEntry : getResponses.entrySet()) {
+ messageBuilder.append(String.format("GET response for message %02d: ", getResponseEntry.getKey()))
+ .append(getResponseEntry.getValue()).append('\n');
+ }
+ LOGGER.info(messageBuilder.toString());
+
+ for (Map.Entry<Integer, String> postResponseEntry : postResponses.entrySet()) {
+ assertEquals(
+ String.format("Unexpected POST notification response for message %02d", postResponseEntry.getKey()),
+ expectedPostResponse, postResponseEntry.getValue());
+ }
+
+ final List<Integer> lost = new LinkedList<Integer>();
+ final Collection<String> getResponseValues = getResponses.values();
+ for (int i = 0; i < MAX_MESSAGES; i++) {
+ if (!getResponseValues.contains(String.format("%02d", i))) {
+ lost.add(i);
+ }
+ }
+ if (!lost.isEmpty()) {
+ fail("Detected a posted message loss(es): " + lost.toString());
+ }
+ assertEquals(MAX_MESSAGES, postResponses.size());
+ assertEquals(MAX_MESSAGES, getResponses.size());
+ }
+
+ @Test
+ public void testLongRunningResource() throws InterruptedException {
+ final WebTarget resourceTarget = target().path(App.ASYNC_LONG_RUNNING_OP_PATH);
+ final String expectedResponse = SimpleLongRunningResource.NOTIFICATION_RESPONSE;
+
+ final int MAX_MESSAGES = 100;
+ final int LATCH_WAIT_TIMEOUT = 25 * getAsyncTimeoutMultiplier();
+ final boolean debugMode = false;
+ final boolean sequentialGet = false;
+ final Object sequentialGetLock = new Object();
+
+ final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+ .setNameFormat("async-resource-test-%02d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+
+ final Map<Integer, String> getResponses = new ConcurrentHashMap<Integer, String>();
+
+ final CountDownLatch getRequestLatch = new CountDownLatch(MAX_MESSAGES);
+
+ try {
+ for (int i = 0; i < MAX_MESSAGES; i++) {
+ final int requestId = i;
+ executor.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ if (debugMode || sequentialGet) {
+ synchronized (sequentialGetLock) {
+ get();
+ }
+ } else {
+ get();
+ }
+ }
+
+ private void get() {
+ try {
+ int attemptCounter = 0;
+ while (true) {
+ attemptCounter++;
+ try {
+ final String response = resourceTarget.request().get(String.class);
+ getResponses.put(requestId, response);
+ break;
+ } catch (Throwable t) {
+ LOGGER.log(Level.SEVERE, String.format("Error sending GET request <%s> for %d. time.",
+ requestId, attemptCounter), t);
+ }
+ if (attemptCounter > 3) {
+ break;
+ }
+ Thread.sleep(10);
+ }
+ } catch (InterruptedException ignored) {
+ LOGGER.log(Level.WARNING,
+ String.format("Error sending GET message <%s>: Interrupted", requestId), ignored);
+ } finally {
+ getRequestLatch.countDown();
+ }
+ }
+ });
+ }
+
+ if (debugMode) {
+ getRequestLatch.await();
+ } else {
+ if (!getRequestLatch.await(LATCH_WAIT_TIMEOUT, TimeUnit.SECONDS)) {
+ LOGGER.log(Level.SEVERE, "Waiting for all GET requests to complete has timed out.");
+ }
+ }
+ } finally {
+ executor.shutdownNow();
+ }
+
+ final ArrayList<Map.Entry<Integer, String>> responseEntryList =
+ new ArrayList<Map.Entry<Integer, String>>(getResponses.entrySet());
+ Collections.sort(responseEntryList,
+ new Comparator<Map.Entry<Integer, String>>() {
+ @Override
+ public int compare(Map.Entry<Integer, String> o1, Map.Entry<Integer, String> o2) {
+ return o1.getKey().compareTo(o2.getKey());
+ }
+ });
+ StringBuilder messageBuilder = new StringBuilder("GET responses received: ").append(responseEntryList.size())
+ .append("\n");
+ for (Map.Entry<Integer, String> getResponseEntry : responseEntryList) {
+ messageBuilder.append(String.format("GET response for message %02d: ", getResponseEntry.getKey()))
+ .append(getResponseEntry.getValue()).append('\n');
+ }
+ LOGGER.info(messageBuilder.toString());
+
+ for (Map.Entry<Integer, String> entry : responseEntryList) {
+ assertEquals(
+ String.format("Unexpected GET notification response for message %02d", entry.getKey()),
+ expectedResponse, entry.getValue());
+ }
+ assertEquals(MAX_MESSAGES, getResponses.size());
+ }
+
+}
diff --git a/examples/server-sent-events-jaxrs/README.MD b/examples/server-sent-events-jaxrs/README.MD
new file mode 100644
index 0000000..010baaa
--- /dev/null
+++ b/examples/server-sent-events-jaxrs/README.MD
@@ -0,0 +1,70 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Server-Sent Events (SSE) Example
+================================
+
+This example demonstrates JAX-RS 2.1 server-sent events support.
+
+The full description how to add support for server-sent events to your application can be found in Jersey User Guide,
+chapter
+[Server-Sent Events (SSE) Support](https://jersey.java.net/documentation/latest/sse.html).
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Description
+------------------------------------- | -------------------------- | -------------- | ------------
+**_server-sent-events_** | ServerSentEventsResource | GET | Get entire EventOutput with all messages
+**_server-sent-events_** | ServerSentEventsResource | POST | Insert a new message in EventOutput
+**_server-sent-events_** | ServerSentEventsResource | DELETE | Reset EventOutput
+**_server-sent-events/domains/{id}_** | BlockingPostChatResource | POST | Generate several messages with a delay in EventOutput and return it
+
+Sample Response
+---------------
+
+A great example of Server-Sent Events is `server-sent-events/domains/{id}` which sends several messages with a delay 200ms
+between each other.
+
+> curl -v -X POST http://localhost:8080/server-sent-events-jaxrs/domains/1 -H "Content-Type: text/event-stream"
+
+Look at a console how events are handled one after another in the right order.
+
+```
+event: domain-progress
+data: starting domain 1 ...
+
+event: domain-progress
+data: 50%
+
+event: domain-progress
+data: 60%
+
+event: domain-progress
+data: 70%
+
+event: domain-progress
+data: 99%
+
+event: domain-progress
+data: done
+```
+
+Running the Example
+-------------------
+
+Look at Jersey Documentation to learn how to consume Server-Sent Events using Jersey Client
+<https://jersey.java.net/documentation/latest/sse.html#d0e11786>
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
diff --git a/examples/server-sent-events-jaxrs/pom.xml b/examples/server-sent-events-jaxrs/pom.xml
new file mode 100644
index 0000000..ade2a8f
--- /dev/null
+++ b/examples/server-sent-events-jaxrs/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>server-sent-events-jaxrs</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-server-sent-events-jaxrs</name>
+
+ <description>Jersey JAX-RS 2.1 Server-Sent Events example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-sse</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/server-sent-events-jaxrs/src/main/java/org/glassfish/jersey/examples/sse/jaxrs/App.java b/examples/server-sent-events-jaxrs/src/main/java/org/glassfish/jersey/examples/sse/jaxrs/App.java
new file mode 100644
index 0000000..29d44ff
--- /dev/null
+++ b/examples/server-sent-events-jaxrs/src/main/java/org/glassfish/jersey/examples/sse/jaxrs/App.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sse.jaxrs;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Server sent event example.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/");
+ static final String ROOT_PATH = "server-sent-events";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"JAX-RS 2.1 Server-Sent Events\" Jersey Example App");
+
+ final ResourceConfig resourceConfig = new ResourceConfig(JaxRsServerSentEventsResource.class);
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, false);
+ Runtime.getRuntime().addShutdownHook(new Thread(server::shutdownNow));
+ server.start();
+
+ System.out.println(String.format("Application started.\nTry out %s%s\nStop the application using CTRL+C",
+ BASE_URI, ROOT_PATH));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+}
diff --git a/examples/server-sent-events-jaxrs/src/main/java/org/glassfish/jersey/examples/sse/jaxrs/DomainResource.java b/examples/server-sent-events-jaxrs/src/main/java/org/glassfish/jersey/examples/sse/jaxrs/DomainResource.java
new file mode 100644
index 0000000..98a03e0
--- /dev/null
+++ b/examples/server-sent-events-jaxrs/src/main/java/org/glassfish/jersey/examples/sse/jaxrs/DomainResource.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sse.jaxrs;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.sse.Sse;
+import javax.ws.rs.sse.SseBroadcaster;
+import javax.ws.rs.sse.SseEventSink;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@Path("domain")
+public class DomainResource {
+
+ private static final Map<Integer, Process> processes = new ConcurrentHashMap<>();
+
+ @Path("start")
+ @POST
+ public Response post(@DefaultValue("0") @QueryParam("testSources") int testSources, @Context Sse sse) {
+ final Process process = new Process(testSources, sse);
+ processes.put(process.getId(), process);
+
+ Executors.newSingleThreadExecutor().execute(process);
+
+ final URI processIdUri = UriBuilder.fromResource(DomainResource.class).path("process/{id}").build(process.getId());
+ return Response.created(processIdUri).build();
+ }
+
+ @Path("process/{id}")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ @GET
+ public void getProgress(@PathParam("id") int id,
+ @DefaultValue("false") @QueryParam("testSource") boolean testSource,
+ @Context SseEventSink eventSink) {
+ final Process process = processes.get(id);
+
+ if (process != null) {
+ if (testSource) {
+ process.release();
+ }
+ process.getBroadcaster().register(eventSink);
+ } else {
+ throw new NotFoundException();
+ }
+ }
+
+ static class Process implements Runnable {
+
+ private static final AtomicInteger counter = new AtomicInteger(0);
+
+ private final int id;
+ private final CountDownLatch latch;
+ private final SseBroadcaster broadcaster;
+ private final Sse sse;
+
+ Process(int testReceivers, Sse sse) {
+ this.sse = sse;
+ this.broadcaster = sse.newBroadcaster();
+ id = counter.incrementAndGet();
+ latch = testReceivers > 0 ? new CountDownLatch(testReceivers) : null;
+ }
+
+ int getId() {
+ return id;
+ }
+
+ SseBroadcaster getBroadcaster() {
+ return broadcaster;
+ }
+
+ void release() {
+ if (latch != null) {
+ latch.countDown();
+ }
+ }
+
+ public void run() {
+ try {
+ if (latch != null) {
+ // wait for all test EventSources to be registered
+ latch.await(5, TimeUnit.SECONDS);
+ }
+
+ broadcaster.broadcast(sse.newEventBuilder()
+ .name("domain-progress")
+ .data(String.class, "starting domain " + id + " ...")
+ .build());
+ broadcaster.broadcast(sse.newEventBuilder().name("domain-progress").data(String.class, "50%").build());
+ broadcaster.broadcast(sse.newEventBuilder().name("domain-progress").data(String.class, "60%").build());
+ broadcaster.broadcast(sse.newEventBuilder().name("domain-progress").data(String.class, "70%").build());
+ broadcaster.broadcast(sse.newEventBuilder().name("domain-progress").data(String.class, "99%").build());
+ broadcaster.broadcast(sse.newEventBuilder().name("domain-progress").data(String.class, "done").build());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/examples/server-sent-events-jaxrs/src/main/java/org/glassfish/jersey/examples/sse/jaxrs/JaxRsServerSentEventsResource.java b/examples/server-sent-events-jaxrs/src/main/java/org/glassfish/jersey/examples/sse/jaxrs/JaxRsServerSentEventsResource.java
new file mode 100644
index 0000000..28ab750
--- /dev/null
+++ b/examples/server-sent-events-jaxrs/src/main/java/org/glassfish/jersey/examples/sse/jaxrs/JaxRsServerSentEventsResource.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sse.jaxrs;
+
+import java.io.IOException;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+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.SseEventSink;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@Path("server-sent-events")
+public class JaxRsServerSentEventsResource {
+
+ private static volatile SseEventSink eventSink = null;
+
+ @GET
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void getMessageQueue(@Context SseEventSink sink) {
+ eventSink = sink;
+ }
+
+ @POST
+ public void addMessage(final String message, @Context Sse sse) throws IOException {
+ final SseEventSink localSink = eventSink;
+ if (localSink != null) {
+ localSink.send(sse.newEventBuilder().name("custom-message").data(String.class, message).build());
+ }
+ }
+
+ @DELETE
+ public void close() throws IOException {
+ final SseEventSink localSink = eventSink;
+ if (localSink != null) {
+ eventSink.close();
+ }
+ eventSink = null;
+ }
+
+ @POST
+ @Path("domains/{id}")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void startDomain(@PathParam("id") final String id, @Context SseEventSink domainSink, @Context Sse sse) {
+ new Thread(() -> {
+ try {
+ domainSink.send(sse.newEventBuilder()
+ .name("domain-progress")
+ .data(String.class, "starting domain " + id + " ...")
+ .build());
+ Thread.sleep(200);
+ domainSink.send(sse.newEventBuilder().name("domain-progress").data(String.class, "50%").build());
+ Thread.sleep(200);
+ domainSink.send(sse.newEventBuilder().name("domain-progress").data(String.class, "60%").build());
+ Thread.sleep(200);
+ domainSink.send(sse.newEventBuilder().name("domain-progress").data(String.class, "70%").build());
+ Thread.sleep(200);
+ domainSink.send(sse.newEventBuilder().name("domain-progress").data(String.class, "99%").build());
+ Thread.sleep(200);
+ domainSink.send(sse.newEventBuilder().name("domain-progress").data(String.class, "done").build());
+ domainSink.close();
+
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ }).start();
+ }
+}
diff --git a/examples/server-sent-events-jaxrs/src/test/java/org/glassfish/jersey/examples/sse/jaxrs/ServerSentEventsTest.java b/examples/server-sent-events-jaxrs/src/test/java/org/glassfish/jersey/examples/sse/jaxrs/ServerSentEventsTest.java
new file mode 100644
index 0000000..b0e234a
--- /dev/null
+++ b/examples/server-sent-events-jaxrs/src/test/java/org/glassfish/jersey/examples/sse/jaxrs/ServerSentEventsTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sse.jaxrs;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.sse.SseEventSource;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * SSE example resources test.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public class ServerSentEventsTest extends JerseyTest {
+
+ private final ExecutorService executorService =
+ Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("ServerSentEventsTest-%d").build());
+ private final Client client = ClientBuilder.newBuilder().executorService(executorService).build();
+
+ @Override
+ protected Application configure() {
+ // enable(TestProperties.LOG_TRAFFIC);
+ return new ResourceConfig(JaxRsServerSentEventsResource.class, DomainResource.class);
+ }
+
+ @Override
+ protected Client getClient() {
+ return client;
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ client.close();
+ executorService.shutdown();
+ }
+
+ /**
+ * Test consuming a single SSE event via event source.
+ *
+ * @throws Exception in case of a failure during the test execution.
+ */
+ @Test
+ public void testEventSource() throws Exception {
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicReference<String> message = new AtomicReference<>();
+ final SseEventSource eventSource = SseEventSource.target(target().path(App.ROOT_PATH)).build();
+ eventSource.register((inboundEvent) -> {
+ final String value = inboundEvent.readData();
+ message.set(value);
+ latch.countDown();
+ });
+
+ eventSource.open();
+ target().path(App.ROOT_PATH).request().post(Entity.text("message"));
+
+ try {
+ assertTrue("Waiting for message to be delivered has timed out.",
+ latch.await(5 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS));
+ } finally {
+ eventSource.close();
+ }
+ assertThat("Unexpected SSE event data value.", message.get(), equalTo("message"));
+ }
+
+
+ /**
+ * Test receiving all streamed messages in parallel by multiple event sources.
+ *
+ * @throws Exception in case of a failure during the test execution.
+ */
+ @Test
+ public void testCreateDomain() throws Exception {
+ final int MAX_CLIENTS = 25;
+ final int MESSAGE_COUNT = 6;
+
+ final Response response = target().path("domain/start")
+ .queryParam("testSources", MAX_CLIENTS)
+ .request().post(Entity.text("data"), Response.class);
+
+ assertThat("Unexpected start domain response status code.",
+ response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
+
+ final Map<Integer, Integer> messageCounts = new ConcurrentHashMap<>(MAX_CLIENTS);
+ final CountDownLatch doneLatch = new CountDownLatch(MAX_CLIENTS);
+ final SseEventSource[] sources = new SseEventSource[MAX_CLIENTS];
+
+ final String processUriString = target().getUri().relativize(response.getLocation()).toString();
+
+ final WebTarget sseTarget = target().path(processUriString).queryParam("testSource", "true");
+ for (int i = 0; i < MAX_CLIENTS; i++) {
+ final AtomicInteger messageCount = new AtomicInteger(0); // will this work?
+ final int id = i;
+ sources[id] = SseEventSource.target(sseTarget).build();
+ sources[id].register((event) -> {
+ messageCount.incrementAndGet();
+ final String message = event.readData(String.class);
+ if ("done".equals(message)) {
+ messageCounts.put(id, messageCount.get());
+ doneLatch.countDown();
+ }
+ });
+ sources[i].open();
+ }
+
+ doneLatch.await(5 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS);
+
+ for (SseEventSource source : sources) {
+ source.close();
+ }
+
+ for (int i = 0; i < MAX_CLIENTS; i++) {
+ final Integer count = messageCounts.get(i);
+ assertThat("Final message not received by event source " + i, count, notNullValue());
+ assertThat("Unexpected number of messages received by event source " + i,
+ count, equalTo(MESSAGE_COUNT));
+ }
+ }
+}
diff --git a/examples/server-sent-events-jersey/README.MD b/examples/server-sent-events-jersey/README.MD
new file mode 100644
index 0000000..8c12344
--- /dev/null
+++ b/examples/server-sent-events-jersey/README.MD
@@ -0,0 +1,72 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Server-Sent Events (SSE) Example
+===================================
+
+This example demonstrates JAX-RS 2.0 server-sent events support sometimes also called one-way publish-subscribe model.
+
+The full description how to create a support for server-sent events can be found in Jersey User Guide, chapter
+[Server-Sent Events (SSE) Support](https://jersey.java.net/documentation/latest/sse.html).
+
+It is highly recommended to look at details of SSE API Specification on
+<http://www.w3.org/TR/2009/WD-eventsource-20091029/>
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods | Description
+------------------------------------- | -------------------------- | -------------- | ------------
+**_server-sent-events_** | ServerSentEventsResource | GET | Get entire EventOutput with all messages
+**_server-sent-events_** | ServerSentEventsResource | POST | Insert a new message in EventOutput
+**_server-sent-events_** | ServerSentEventsResource | DELETE | Reset EventOutput
+**_server-sent-events/domains/{id}_** | BlockingPostChatResource | POST | Generate several messages with a delay in EventOutput and return it
+
+Sample Response
+---------------
+
+A great example of Server-Sent Events is `server-sent-events/domains/{id}` which sends several messages with a delay 200ms
+between each other.
+
+> curl -v -X POST http://localhost:8080/server-sent-events-jersey/domains/1 -H "Content-Type: text/event-stream"
+
+Look at a console how events are handled one after another in the right order.
+
+```
+event: domain-progress
+data: starting domain 1 ...
+
+event: domain-progress
+data: 50%
+
+event: domain-progress
+data: 60%
+
+event: domain-progress
+data: 70%
+
+event: domain-progress
+data: 99%
+
+event: domain-progress
+data: done
+```
+
+Running the Example
+-------------------
+
+Look at Jersey Documentation to learn how to consume Server-Sent Events using Jersey Client
+<https://jersey.java.net/documentation/latest/sse.html#d0e11786>
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
diff --git a/examples/server-sent-events-jersey/pom.xml b/examples/server-sent-events-jersey/pom.xml
new file mode 100644
index 0000000..af580c1
--- /dev/null
+++ b/examples/server-sent-events-jersey/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>server-sent-events-jersey</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-server-sent-events-jersey</name>
+
+ <description>Jersey Server-Sent Events example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-sse</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/server-sent-events-jersey/src/main/java/org/glassfish/jersey/examples/sse/jersey/App.java b/examples/server-sent-events-jersey/src/main/java/org/glassfish/jersey/examples/sse/jersey/App.java
new file mode 100644
index 0000000..c253d6c
--- /dev/null
+++ b/examples/server-sent-events-jersey/src/main/java/org/glassfish/jersey/examples/sse/jersey/App.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sse.jersey;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.media.sse.SseFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Server sent event example.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/");
+ public static final String ROOT_PATH = "server-sent-events";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("\"Server-Sent Events\" Jersey Example App");
+
+ final ResourceConfig resourceConfig = new ResourceConfig(ServerSentEventsResource.class, SseFeature.class);
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.\nTry out %s%s\nStop the application using CTRL+C",
+ BASE_URI, ROOT_PATH));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+}
diff --git a/examples/server-sent-events-jersey/src/main/java/org/glassfish/jersey/examples/sse/jersey/DomainResource.java b/examples/server-sent-events-jersey/src/main/java/org/glassfish/jersey/examples/sse/jersey/DomainResource.java
new file mode 100644
index 0000000..d51e916
--- /dev/null
+++ b/examples/server-sent-events-jersey/src/main/java/org/glassfish/jersey/examples/sse/jersey/DomainResource.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sse.jersey;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.media.sse.EventOutput;
+import org.glassfish.jersey.media.sse.OutboundEvent;
+import org.glassfish.jersey.media.sse.SseBroadcaster;
+import org.glassfish.jersey.media.sse.SseFeature;
+import org.glassfish.jersey.server.ChunkedOutput;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Path("domain")
+public class DomainResource {
+
+ private static final Map<Integer, Process> processes = new ConcurrentHashMap<Integer, Process>();
+
+ @Path("start")
+ @POST
+ public Response post(@DefaultValue("0") @QueryParam("testSources") int testSources) {
+ final Process process = new Process(testSources);
+ processes.put(process.getId(), process);
+
+ Executors.newSingleThreadExecutor().execute(process);
+
+ final URI processIdUri = UriBuilder.fromResource(DomainResource.class).path("process/{id}").build(process.getId());
+ return Response.created(processIdUri).build();
+ }
+
+ @Path("process/{id}")
+ @Produces(SseFeature.SERVER_SENT_EVENTS)
+ @GET
+ public EventOutput getProgress(@PathParam("id") int id,
+ @DefaultValue("false") @QueryParam("testSource") boolean testSource) {
+ final Process process = processes.get(id);
+
+ if (process != null) {
+ if (testSource) {
+ process.release();
+ }
+ final EventOutput eventOutput = new EventOutput();
+ process.getBroadcaster().add(eventOutput);
+ return eventOutput;
+ } else {
+ throw new NotFoundException();
+ }
+ }
+
+ static class Process implements Runnable {
+
+ private static final AtomicInteger counter = new AtomicInteger(0);
+
+ private final int id;
+ private final CountDownLatch latch;
+ private final SseBroadcaster broadcaster = new SseBroadcaster() {
+ @Override
+ public void onException(ChunkedOutput<OutboundEvent> outboundEventChunkedOutput, Exception exception) {
+ exception.printStackTrace();
+ }
+ };
+
+ public Process(int testReceivers) {
+ id = counter.incrementAndGet();
+ latch = testReceivers > 0 ? new CountDownLatch(testReceivers) : null;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public SseBroadcaster getBroadcaster() {
+ return broadcaster;
+ }
+
+ public boolean release() {
+ if (latch == null) {
+ return false;
+ }
+
+ latch.countDown();
+ return true;
+ }
+
+ public void run() {
+ try {
+ if (latch != null) {
+ // wait for all test EventSources to be registered
+ latch.await(5, TimeUnit.SECONDS);
+ }
+
+ broadcaster.broadcast(
+ new OutboundEvent.Builder().name("domain-progress").data(String.class, "starting domain " + id + " ...")
+ .build());
+ broadcaster.broadcast(new OutboundEvent.Builder().name("domain-progress").data(String.class, "50%").build());
+ broadcaster.broadcast(new OutboundEvent.Builder().name("domain-progress").data(String.class, "60%").build());
+ broadcaster.broadcast(new OutboundEvent.Builder().name("domain-progress").data(String.class, "70%").build());
+ broadcaster.broadcast(new OutboundEvent.Builder().name("domain-progress").data(String.class, "99%").build());
+ broadcaster.broadcast(new OutboundEvent.Builder().name("domain-progress").data(String.class, "done").build());
+ broadcaster.closeAll();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/examples/server-sent-events-jersey/src/main/java/org/glassfish/jersey/examples/sse/jersey/ServerSentEventsResource.java b/examples/server-sent-events-jersey/src/main/java/org/glassfish/jersey/examples/sse/jersey/ServerSentEventsResource.java
new file mode 100644
index 0000000..1b8096d
--- /dev/null
+++ b/examples/server-sent-events-jersey/src/main/java/org/glassfish/jersey/examples/sse/jersey/ServerSentEventsResource.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sse.jersey;
+
+import java.io.IOException;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+
+import org.glassfish.jersey.media.sse.EventOutput;
+import org.glassfish.jersey.media.sse.OutboundEvent;
+import org.glassfish.jersey.media.sse.SseFeature;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@Path("server-sent-events")
+public class ServerSentEventsResource {
+
+ private static volatile EventOutput eventOutput = new EventOutput();
+
+ @GET
+ @Produces(SseFeature.SERVER_SENT_EVENTS)
+ public EventOutput getMessageQueue() {
+ return eventOutput;
+ }
+
+ @POST
+ public void addMessage(final String message) throws IOException {
+ final EventOutput localOutput = eventOutput;
+ if (localOutput != null) {
+ eventOutput.write(new OutboundEvent.Builder().name("custom-message").data(String.class, message).build());
+ }
+ }
+
+ @DELETE
+ public void close() throws IOException {
+ final EventOutput localOutput = eventOutput;
+ if (localOutput != null) {
+ eventOutput.close();
+ }
+ ServerSentEventsResource.setEventOutput(new EventOutput());
+ }
+
+ @POST
+ @Path("domains/{id}")
+ @Produces(SseFeature.SERVER_SENT_EVENTS)
+ public EventOutput startDomain(@PathParam("id") final String id) {
+ final EventOutput seq = new EventOutput();
+
+ new Thread() {
+ public void run() {
+ try {
+ seq.write(new OutboundEvent.Builder().name("domain-progress")
+ .data(String.class, "starting domain " + id + " ...").build());
+ Thread.sleep(200);
+ seq.write(new OutboundEvent.Builder().name("domain-progress").data(String.class, "50%").build());
+ Thread.sleep(200);
+ seq.write(new OutboundEvent.Builder().name("domain-progress").data(String.class, "60%").build());
+ Thread.sleep(200);
+ seq.write(new OutboundEvent.Builder().name("domain-progress").data(String.class, "70%").build());
+ Thread.sleep(200);
+ seq.write(new OutboundEvent.Builder().name("domain-progress").data(String.class, "99%").build());
+ Thread.sleep(200);
+ seq.write(new OutboundEvent.Builder().name("domain-progress").data(String.class, "done").build());
+ seq.close();
+
+ } catch (final InterruptedException | IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }.start();
+
+ return seq;
+ }
+
+ private static void setEventOutput(final EventOutput eventOutput) {
+ ServerSentEventsResource.eventOutput = eventOutput;
+ }
+}
diff --git a/examples/server-sent-events-jersey/src/test/java/org/glassfish/jersey/examples/sse/jersey/ServerSentEventsTest.java b/examples/server-sent-events-jersey/src/test/java/org/glassfish/jersey/examples/sse/jersey/ServerSentEventsTest.java
new file mode 100644
index 0000000..4782e7d
--- /dev/null
+++ b/examples/server-sent-events-jersey/src/test/java/org/glassfish/jersey/examples/sse/jersey/ServerSentEventsTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sse.jersey;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.media.sse.EventInput;
+import org.glassfish.jersey.media.sse.EventListener;
+import org.glassfish.jersey.media.sse.EventSource;
+import org.glassfish.jersey.media.sse.InboundEvent;
+import org.glassfish.jersey.media.sse.SseFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * SSE example resources test.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class ServerSentEventsTest extends JerseyTest {
+ // TODO - due to JdkConnector migration this was radically reduced. It deadlocks with 25 clients, find out why!
+ private static final int MAX_CLIENTS = 10;
+
+ @Override
+ protected Application configure() {
+ // enable(TestProperties.LOG_TRAFFIC);
+ return new ResourceConfig(ServerSentEventsResource.class, DomainResource.class, SseFeature.class);
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.property(ClientProperties.ASYNC_THREADPOOL_SIZE, MAX_CLIENTS + 2);
+ }
+
+ /**
+ * Test consuming a single SSE event via event source.
+ *
+ * @throws Exception in case of a failure during the test execution.
+ */
+ @Test
+ public void testEventSource() throws Exception {
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicReference<String> message = new AtomicReference<String>();
+ final EventSource eventSource = new EventSource(target().path(App.ROOT_PATH)) {
+ @Override
+ public void onEvent(InboundEvent inboundEvent) {
+ try {
+ final String value = inboundEvent.readData();
+ message.set(value);
+ latch.countDown();
+ } catch (ProcessingException e) {
+ e.printStackTrace();
+ }
+ }
+ };
+
+ target().path(App.ROOT_PATH).request().post(Entity.text("message"));
+
+ try {
+ assertTrue("Waiting for message to be delivered has timed out.",
+ latch.await(5 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS));
+ } finally {
+ eventSource.close();
+ }
+ assertThat("Unexpected SSE event data value.", message.get(), equalTo("message"));
+ }
+
+ /**
+ * Test consuming multiple SSE events sequentially using event input.
+ *
+ * @throws Exception in case of a failure during the test execution.
+ */
+ @Test
+ public void testInboundEventReader() throws Exception {
+ final int MAX_MESSAGES = 5;
+ final CountDownLatch startLatch = new CountDownLatch(1);
+
+ final ExecutorService executor = Executors.newSingleThreadExecutor();
+ try {
+ final Future<List<String>> futureMessages =
+ executor.submit(new Callable<List<String>>() {
+
+ @Override
+ public List<String> call() throws Exception {
+ final EventInput eventInput = target(App.ROOT_PATH).register(SseFeature.class)
+ .request().get(EventInput.class);
+
+ startLatch.countDown();
+
+ final List<String> messages = new ArrayList<String>(MAX_MESSAGES);
+ try {
+ for (int i = 0; i < MAX_MESSAGES; i++) {
+ InboundEvent event = eventInput.read();
+ messages.add(event.readData());
+ }
+ } finally {
+ if (eventInput != null) {
+ eventInput.close();
+ }
+ }
+
+ return messages;
+ }
+ });
+
+ assertTrue("Waiting for receiver thread to start has timed out.",
+ startLatch.await(5, TimeUnit.SECONDS));
+
+ for (int i = 0; i < MAX_MESSAGES; i++) {
+ target(App.ROOT_PATH).request().post(Entity.text("message " + i));
+ }
+
+ int i = 0;
+ for (String message : futureMessages.get(5000, TimeUnit.SECONDS)) {
+ assertThat("Unexpected SSE event data value.", message, equalTo("message " + i++));
+ }
+ } finally {
+ executor.shutdownNow();
+ }
+ }
+
+ /**
+ * Test receiving all streamed messages in parallel by multiple event sources.
+ *
+ * @throws Exception in case of a failure during the test execution.
+ */
+ @Test
+ public void testCreateDomain() throws Exception {
+ final int MESSAGE_COUNT = 6;
+
+ final Response response = target().path("domain/start")
+ .queryParam("testSources", MAX_CLIENTS)
+ .request().post(Entity.text("data"), Response.class);
+
+ assertThat("Unexpected start domain response status code.",
+ response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
+
+ final Map<Integer, Integer> messageCounts = new ConcurrentHashMap<Integer, Integer>(MAX_CLIENTS);
+ final CountDownLatch doneLatch = new CountDownLatch(MAX_CLIENTS);
+ final EventSource[] sources = new EventSource[MAX_CLIENTS];
+
+ final String processUriString = target().getUri().relativize(response.getLocation()).toString();
+
+ final WebTarget sseTarget = target().path(processUriString).queryParam("testSource", "true");
+ for (int i = 0; i < MAX_CLIENTS; i++) {
+ final int id = i;
+ sources[id] = EventSource.target(sseTarget).build();
+ sources[id].register(new EventListener() {
+
+ private final AtomicInteger messageCount = new AtomicInteger(0);
+
+ @Override
+ public void onEvent(InboundEvent inboundEvent) {
+ messageCount.incrementAndGet();
+ final String message = inboundEvent.readData(String.class);
+ if ("done".equals(message)) {
+ messageCounts.put(id, messageCount.get());
+ doneLatch.countDown();
+ }
+ }
+ });
+ sources[i].open();
+ }
+
+ doneLatch.await(5 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS);
+
+ for (EventSource source : sources) {
+ source.close();
+ }
+
+ for (int i = 0; i < MAX_CLIENTS; i++) {
+ final Integer count = messageCounts.get(i);
+ assertThat("Final message not received by event source " + i, count, notNullValue());
+ assertThat("Unexpected number of messages received by event source " + i,
+ count, equalTo(MESSAGE_COUNT));
+ }
+ }
+}
diff --git a/examples/servlet3-webapp/README.MD b/examples/servlet3-webapp/README.MD
new file mode 100644
index 0000000..839f789
--- /dev/null
+++ b/examples/servlet3-webapp/README.MD
@@ -0,0 +1,35 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Servlet 3 Example
+===================
+
+This example demonstrates how to use advantages of Servlet v3 and how to configure Jersey application with this version of Jersey.
+JAX-RS resource returns the usual text
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+-------------------- | ------------------- | ------------
+**_/cat_** | CatResource | GET
+**_/dog_** | DogResource | GET
+
+Running the Example
+-------------------
+
+Run the example using Jetty as follows:
+
+> mvn clean package jetty:run
+
+Go to the URL:
+
+- <http://localhost:9998/animals/cat>
+- <http://localhost:9998/animals/dog>
diff --git a/examples/servlet3-webapp/pom.xml b/examples/servlet3-webapp/pom.xml
new file mode 100644
index 0000000..13acfb2
--- /dev/null
+++ b/examples/servlet3-webapp/pom.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>servlet3-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-servlet3-webapp</name>
+
+ <description>Jersey Servlet 3 example with missing servlet-class in the web.xml file</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>${servlet3.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-servlet</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skipTests>true</skipTests>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>jetty-maven-plugin</artifactId>
+ <configuration>
+ <skip>${skip.tests}</skip>
+ <scanIntervalSeconds>10</scanIntervalSeconds>
+ <stopPort>9999</stopPort>
+ <stopKey>STOP</stopKey>
+ <webApp>
+ <contextPath>/</contextPath>
+ <webInfIncludeJarPattern>.*/.*jersey-[^/]\.jar$</webInfIncludeJarPattern>
+ </webApp>
+ <connectors>
+ <connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
+ <port>${jersey.config.test.container.port}</port>
+ <maxIdleTime>60000</maxIdleTime>
+ </connector>
+ </connectors>
+ </configuration>
+ <executions>
+ <execution>
+ <id>start-jetty</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <scanIntervalSeconds>0</scanIntervalSeconds>
+ <daemon>true</daemon>
+ </configuration>
+ </execution>
+ <execution>
+ <id>stop-jetty</id>
+ <phase>post-integration-test</phase>
+ <goals>
+ <goal>stop</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <properties>
+ <jersey.config.test.container.port>9998</jersey.config.test.container.port>
+ </properties>
+
+</project>
diff --git a/examples/servlet3-webapp/src/main/java/org/glassfish/jersey/examples/servlet3/webapp/App.java b/examples/servlet3-webapp/src/main/java/org/glassfish/jersey/examples/servlet3/webapp/App.java
new file mode 100644
index 0000000..1e3f5d5
--- /dev/null
+++ b/examples/servlet3-webapp/src/main/java/org/glassfish/jersey/examples/servlet3/webapp/App.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.servlet3.webapp;
+
+import javax.ws.rs.core.Application;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Test Application subclass for servlet3-webapp example.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public class App extends Application {
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ HashSet<Class<?>> classes = new HashSet<Class<?>>();
+ classes.add(DogResource.class);
+ classes.add(CatResource.class);
+
+ return classes;
+ }
+}
diff --git a/examples/servlet3-webapp/src/main/java/org/glassfish/jersey/examples/servlet3/webapp/CatResource.java b/examples/servlet3-webapp/src/main/java/org/glassfish/jersey/examples/servlet3/webapp/CatResource.java
new file mode 100644
index 0000000..d42071d
--- /dev/null
+++ b/examples/servlet3-webapp/src/main/java/org/glassfish/jersey/examples/servlet3/webapp/CatResource.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.servlet3.webapp;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * Test resource for the servlet3-webapp example.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@Path("cat")
+public class CatResource {
+ @GET
+ @Produces("text/plain")
+ public String meow() {
+ return "Miaow!";
+ }
+}
diff --git a/examples/servlet3-webapp/src/main/java/org/glassfish/jersey/examples/servlet3/webapp/DogResource.java b/examples/servlet3-webapp/src/main/java/org/glassfish/jersey/examples/servlet3/webapp/DogResource.java
new file mode 100644
index 0000000..352576e
--- /dev/null
+++ b/examples/servlet3-webapp/src/main/java/org/glassfish/jersey/examples/servlet3/webapp/DogResource.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.servlet3.webapp;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * Test resource for the servlet3-webapp example.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@Path("dog")
+public class DogResource {
+ @GET
+ @Produces("text/plain")
+ public String bark() {
+ return "Woof!";
+ }
+}
diff --git a/examples/servlet3-webapp/src/main/webapp/WEB-INF/web.xml b/examples/servlet3-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..91a6614
--- /dev/null
+++ b/examples/servlet3-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
+ <servlet>
+ <servlet-name>org.glassfish.jersey.examples.servlet3.webapp.App</servlet-name>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>org.glassfish.jersey.examples.servlet3.webapp.App</servlet-name>
+ <url-pattern>/animals/*</url-pattern>
+ </servlet-mapping>
+</web-app>
diff --git a/examples/servlet3-webapp/src/test/java/Servlet3WebappITCase.java b/examples/servlet3-webapp/src/test/java/Servlet3WebappITCase.java
new file mode 100644
index 0000000..0552dac
--- /dev/null
+++ b/examples/servlet3-webapp/src/test/java/Servlet3WebappITCase.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.glassfish.jersey.test.external.ExternalTestContainerFactory;
+import org.glassfish.jersey.test.spi.TestContainerException;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+
+import org.junit.Test;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests the servlet3-webapp example.
+ * Integration test launched by maven-jetty-plugin
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public class Servlet3WebappITCase extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ return new Application(); // dummy Application instance for test framework
+ }
+
+ @Override
+ protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
+ return new ExternalTestContainerFactory();
+ }
+
+ @Override
+ protected URI getBaseUri() {
+ return UriBuilder.fromUri(super.getBaseUri()).path("animals").build();
+ }
+
+ @Test
+ public void testClientStringResponse() {
+ String s = target().path("dog").request().get(String.class);
+ assertEquals("Woof!", s);
+
+ s = target().path("cat").request().get(String.class);
+ assertEquals("Miaow!", s);
+ }
+}
diff --git a/examples/simple-console/README.MD b/examples/simple-console/README.MD
new file mode 100644
index 0000000..8581318
--- /dev/null
+++ b/examples/simple-console/README.MD
@@ -0,0 +1,73 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Simple Console Example
+======================
+
+This example demonstrates how to develop RESTful web services with the
+embedded Grizzly server.
+
+Contents
+--------
+
+The example consists of three Java classes and one HTML file:
+
+`org.glassfish.jersey.examples.console.App`
+
+Contains the main function which starts and stops the HTTP server.
+
+`org.glassfish.jersey.examples.console.resources.Colours`
+
+A resource class that can produce two different representations of a
+list of colors. The list can be filtered by a client-supplied
+query parameter. The resource references the Form resource using the
+Path annotation declared on the Colours.getColours method.
+
+`org.glassfish.jersey.examples.console.FormResource`
+
+A resource class that produces a form in response to an HTTP GET,
+processes the content of the form when submitted, and produces a
+table of the submitted values in respose to the form being posted.
+
+`form.html`
+
+A static HTML file that is returned by the Form resource.
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+--------------------- | ---------------- | --------------
+**_/form_** | Form | GET, POST
+**_/form/colours_** | Colours | GET
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container. You can access the application at:
+
+- <http://127.0.0.1:9998/resources/form>
+
+Fill in the form and submit it. Note that the pull-down list of colors
+is populated dynamically. Enter part of a color name in the hint box to
+see a filtered list of colors containing the hint.
+
+You can see the complete list of colors at:
+
+- <http://127.0.0.1:9998/resources/form/colours>
+
+Or see only those colors containing the letter 'r':
+
+- <http://127.0.0.1:9998/resources/form/colours?match=r>
+
+A [WADL description](http://wadl.java.net/#spec) may be accessed at the URL:
+
+- <http://127.0.0.1:9998/resources/application.wadl>
\ No newline at end of file
diff --git a/examples/simple-console/pom.xml b/examples/simple-console/pom.xml
new file mode 100644
index 0000000..fcb5fc8
--- /dev/null
+++ b/examples/simple-console/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>simple-console</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-simple-console</name>
+
+ <description>Jersey Simple Console example</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jettison</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.console.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/simple-console/src/main/java/org/glassfish/jersey/examples/console/App.java b/examples/simple-console/src/main/java/org/glassfish/jersey/examples/console/App.java
new file mode 100644
index 0000000..e350294
--- /dev/null
+++ b/examples/simple-console/src/main/java/org/glassfish/jersey/examples/console/App.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.console;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.jettison.JettisonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+public class App {
+
+ private static int getPort(int defaultPort) {
+ final String port = System.getProperty("jersey.config.test.container.port");
+ if (null != port) {
+ try {
+ return Integer.parseInt(port);
+ } catch (NumberFormatException e) {
+ System.out.println("Value of jersey.config.test.container.port property"
+ + " is not a valid positive integer [" + port + "]."
+ + " Reverting to default [" + defaultPort + "].");
+ }
+ }
+ return defaultPort;
+ }
+
+ private static URI getBaseURI() {
+ return UriBuilder.fromUri("http://localhost/resources").port(getPort(defaultPort)).build();
+ }
+
+ public static final URI BASE_URI = getBaseURI();
+ public static final int defaultPort = 9998;
+
+ public static ResourceConfig createApp() {
+ return new ResourceConfig()
+ .register(new JettisonFeature())
+ .packages("org.glassfish.jersey.examples.console");
+ }
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("Simple Console Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(getBaseURI(), createApp(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(
+ String.format("Application started.%nTry out %s%nStop the application using CTRL+C", BASE_URI + "/form"));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+}
diff --git a/examples/simple-console/src/main/java/org/glassfish/jersey/examples/console/resources/Colours.java b/examples/simple-console/src/main/java/org/glassfish/jersey/examples/console/resources/Colours.java
new file mode 100644
index 0000000..048349a
--- /dev/null
+++ b/examples/simple-console/src/main/java/org/glassfish/jersey/examples/console/resources/Colours.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.console.resources;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+
+import org.codehaus.jettison.json.JSONArray;
+
+/**
+ * A web resource for a list of colours.
+ */
+public class Colours {
+
+ private static String colours[] = {"red", "orange", "yellow", "green", "blue", "indigo", "violet"};
+
+ /**
+ * Returns a list of colours as plain text, one colour per line.
+ * @param filter If not empty, constrains the list of colours to only
+ * those that contain this substring
+ * @return the list of colours matching the filter
+ */
+ @GET
+ @Produces("text/plain")
+ public String getColourListAsText(@QueryParam("match") String filter) {
+ StringBuilder buf = new StringBuilder();
+ for (String colour : getMatchingColours(filter)) {
+ buf.append(colour);
+ buf.append('\n');
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Returns a list of colours as a JSON array.
+ * @param filter If not empty, constrains the list of colours to only
+ * those that contain this substring
+ * @return the list of colours matching the filter
+ */
+ @GET
+ @Produces("application/json")
+ public JSONArray getColourListAsJSON(@QueryParam("match") String filter) {
+ return new JSONArray(getMatchingColours(filter));
+ }
+
+ /**
+ * Returns a list of colours.
+ * @param filter If not empty, constrains the list of colours to only
+ * those that contain this substring
+ * @return the list of colours matching the filter
+ */
+ public static List<String> getMatchingColours(String filter) {
+ List<String> matches = new ArrayList<>();
+
+ for (String colour : colours) {
+ if (filter == null || filter.length() == 0 || colour.contains(filter)) {
+ matches.add(colour);
+ }
+ }
+
+ return matches;
+ }
+}
diff --git a/examples/simple-console/src/main/java/org/glassfish/jersey/examples/console/resources/FormResource.java b/examples/simple-console/src/main/java/org/glassfish/jersey/examples/console/resources/FormResource.java
new file mode 100644
index 0000000..fb7d495
--- /dev/null
+++ b/examples/simple-console/src/main/java/org/glassfish/jersey/examples/console/resources/FormResource.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.console.resources;
+
+import java.io.InputStream;
+import java.util.Date;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.NewCookie;
+import javax.ws.rs.core.Response;
+
+/**
+ * A Web form resource, produces the form and processes the results of
+ * submitting it.
+ *
+ */
+@Path("/form")
+@Produces("text/html")
+public class FormResource {
+
+ private static final Colours coloursResource = new Colours();
+
+ @Context
+ HttpHeaders headers;
+
+ @Path("colours")
+ public Colours getColours() {
+ return coloursResource;
+ }
+
+ /**
+ * Produce a form from a static HTML file packaged with the compiled class
+ * @return a stream from which the HTML form can be read.
+ */
+ @GET
+ public Response getForm() {
+ Date now = new Date();
+
+ InputStream entity = this.getClass().getClassLoader().getResourceAsStream("form.html");
+ return Response.ok(entity)
+ .cookie(new NewCookie("date", now.toString())).build();
+ }
+
+ /**
+ * Process the form submission. Produces a table showing the form field
+ * values submitted.
+ * @return a dynamically generated HTML table.
+ * @param formData the data from the form submission
+ */
+ @POST
+ @Consumes("application/x-www-form-urlencoded")
+ public String processForm(MultivaluedMap<String, String> formData) {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<html><head><title>Form results</title></head><body>");
+ buf.append("<p>Hello, you entered the following information: </p><table border='1'>");
+ for (String key : formData.keySet()) {
+ if (key.equals("submit")) {
+ continue;
+ }
+ buf.append("<tr><td>");
+ buf.append(key);
+ buf.append("</td><td>");
+ buf.append(formData.getFirst(key));
+ buf.append("</td></tr>");
+ }
+ for (Cookie c : headers.getCookies().values()) {
+ buf.append("<tr><td>Cookie: ");
+ buf.append(c.getName());
+ buf.append("</td><td>");
+ buf.append(c.getValue());
+ buf.append("</td></tr>");
+ }
+
+ buf.append("</table></body></html>");
+ return buf.toString();
+ }
+
+}
diff --git a/examples/simple-console/src/main/resources/form.html b/examples/simple-console/src/main/resources/form.html
new file mode 100644
index 0000000..4665455
--- /dev/null
+++ b/examples/simple-console/src/main/resources/form.html
@@ -0,0 +1,67 @@
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html>
+<head>
+ <title>A Form</title>
+ <script type="text/javascript">
+ function setColours() {
+ var http_request = new XMLHttpRequest();
+ if (document.bio.hint.value.length > 0)
+ http_request.open("GET", "form/colours?match="+document.bio.hint.value, false);
+ else
+ http_request.open("GET", "form/colours", false);
+ http_request.setRequestHeader("Accept", "application/json");
+ http_request.send(null);
+ if (http_request.status == 200) {
+ list = eval("(" + http_request.responseText + ")");
+ document.bio.colour.options.length = 0;
+ document.bio.colour.options[0] = new Option("Pick a colour", "", true, false);
+ for (var i = 0; i<list.length; i++) {
+ document.bio.colour.options[i+1] = new Option(list[i], list[i], false, false);
+ }
+ } else {
+ alert("There was a problem fetching the list of colours.");
+ }
+ http_request = null;
+ }
+ </script>
+</head>
+<body>
+<p>Tell me about yourself:</p>
+<form name="bio" action="form" method="POST">
+ <table>
+ <tr>
+ <td align="right">Name:</td>
+ <td><input type="text" name="name" value="" size="30" /></td>
+ </tr>
+ <tr>
+ <td align="right">Favorite colour:</td>
+ <td>
+ <select name="colour" onmousedown="setColours()">
+ <option>Pick a colour</option>
+ </select> <i>Populated dynamically when you click on the control</i><br/>
+ <input type="text" name="hint" value="" size="13"/> <i>Type a hint to reduce the number of options</i>
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>
+ <input type="submit" value="Submit" name="submit" />
+ </td>
+ </tr>
+ </table>
+</form>
+</body>
+</html>
diff --git a/examples/simple-console/src/test/java/org/glassfish/jersey/examples/console/MainTest.java b/examples/simple-console/src/test/java/org/glassfish/jersey/examples/console/MainTest.java
new file mode 100644
index 0000000..14fe277
--- /dev/null
+++ b/examples/simple-console/src/test/java/org/glassfish/jersey/examples/console/MainTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.console;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;
+import org.glassfish.jersey.jettison.JettisonFeature;
+import org.glassfish.jersey.message.internal.MediaTypes;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.codehaus.jettison.json.JSONArray;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Naresh (srinivas.bhimisetty at oracle.com)
+ */
+public class MainTest extends JerseyTest {
+
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+
+ return App.createApp();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(new JettisonFeature());
+ }
+
+ /**
+ * Test if a WADL document is available at the relative path
+ * "application.wadl".
+ */
+ @Test
+ public void testApplicationWadl() {
+ String serviceWadl = target().path("application.wadl").request(MediaTypes.WADL_TYPE).get(String.class);
+ assertTrue(!serviceWadl.isEmpty());
+ }
+
+ /**
+ * Test if GET on the resource "/form" gives response with status code 200.
+ */
+ @Test
+ public void testGetOnForm() {
+ Response response = target().path("form").request(MediaType.TEXT_HTML).get();
+ assertEquals("GET on the 'form' resource doesn't give expected response", Response.Status.OK.getStatusCode(),
+ response.getStatusInfo().getStatusCode());
+ }
+
+ /**
+ * Test checks that POST on the '/form' resource gives a response page
+ * with the entered data.
+ */
+ @Test
+ public void testPostOnForm() {
+ MultivaluedMap<String, String> formData = new MultivaluedStringMap();
+ formData.add("name", "testName");
+ formData.add("colour", "red");
+ formData.add("hint", "re");
+
+ Response response = target().path("form").request().post(Entity.entity(formData, MediaType.APPLICATION_FORM_URLENCODED));
+ assertEquals(Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode());
+
+ // check that the generated response is the expected one
+ InputStream responseInputStream = response.readEntity(InputStream.class);
+ try {
+ byte[] responseData = new byte[responseInputStream.available()];
+ final int read = responseInputStream.read(responseData);
+
+ assertTrue(read > 0);
+ assertTrue(new String(responseData).contains("Hello, you entered"));
+ } catch (IOException ex) {
+ Logger.getLogger(MainTest.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Test checks that a GET on the resource "/form/colours" with mime-type "text/html"
+ * shows the appropriate colours based on the query param "match".
+ */
+ @Test
+ public void testGetColoursAsPlainText() {
+ // without the query param "match"
+ Response response = target().path("form").path("colours").request(MediaType.TEXT_PLAIN).get();
+ assertEquals("GET on path '/form/colours' with mime type 'text/html' doesn't give expected response",
+ Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode());
+
+ String responseMsg = target().path("form").path("colours").request(MediaType.TEXT_PLAIN).get(String.class);
+ assertEquals("Response content doesn't match the expected value", "red\norange\nyellow\ngreen\nblue\nindigo\nviolet\n",
+ responseMsg);
+
+ // with the query param "match" value "re"
+ responseMsg = target("form/colours").queryParam("match", "re").request(MediaType.TEXT_PLAIN).get(String.class);
+ assertEquals("Response content doesn't match the expected value with the query param 'match=re'", "red\ngreen\n",
+ responseMsg);
+ }
+
+ /**
+ * Test checks that a GET on the resource "/form/colours" with mime-type "application/json"
+ * shows the appropriate colours based on the query param "match".
+ */
+ @Test
+ public void testGetColoursAsJson() {
+ Response response = target().path("form").path("colours").request(MediaType.APPLICATION_JSON).get();
+ assertEquals("GET on path '/form/colours' with mime type 'application/json' doesn't give expected response",
+ Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode());
+
+ JSONArray jsonArray = target().path("form").path("colours").request(MediaType.APPLICATION_JSON).get(JSONArray.class);
+ assertEquals("Returned JSONArray doesn't have expected number of entries", 7, jsonArray.length());
+
+ // with the query param "match" value "re"
+ jsonArray = target("form/colours").queryParam("match", "re").request(MediaType.APPLICATION_JSON).get(JSONArray.class);
+ assertEquals("Returned JSONArray doesn't have expected number of entries with the query param 'match=re'", 2,
+ jsonArray.length());
+ }
+
+}
diff --git a/examples/sse-item-store-jaxrs-webapp/README.MD b/examples/sse-item-store-jaxrs-webapp/README.MD
new file mode 100644
index 0000000..2948b07
--- /dev/null
+++ b/examples/sse-item-store-jaxrs-webapp/README.MD
@@ -0,0 +1,67 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+JAX-RS SSE Item Store Jersey Example
+====================================
+
+An example demonstrating how new Server Sent Events (SSE) JAX-RS API can
+be used to notify clients about changes in server-side managed data
+(collection of items). The example also outlines how SSE events can be
+consumed using javascript browser-based clients.
+
+Contents
+--------
+
+The example consists of a web-based client application and a server-side
+application deployed on a [Jetty servlet container](http://www.eclipse.org/jetty/documentation/current/).
+
+The server side part of the application consists of a JAX-RS resource
+managing collection of string items and a simple HTML page that includes
+a browser SSE client written in Javascript as well as a basic CSS
+stylesheet. The SSE Javascript client connects to the JAX-RS resource
+and transforms the streamed messages into HTML code that is rendered by
+the browser. The javascript client also demonstrates how named and
+unnamed SSE events are handled by HTML5 `EventSource` component. The
+mapping of the URI path space of the server-side part of the application
+is presented in the following table:
+
+URI path | Resource class | HTTP methods
+------------------------------- | ------------------- | --------------
+**_/resources/items_** | ItemStoreResource | GET, POST
+**_/resources/items/events_** | ItemStoreResource | GET (SSE)
+
+Application is configured to run using Jetty maven plugin under base
+path `sse-item-store-jaxrs-webapp`.
+
+Running the Example
+-------------------
+
+> mvn clean compile jetty:run
+
+The command above deploys the current example. After successful
+deployment, you should be able to access the browser SSE client page at
+<http://localhost:8080/sse-item-store-jaxrs-webapp/index.html>. To see the raw
+SSE event stream, you may also point your browser directly at the
+[`ItemStoreResource`](http://localhost:8080/sse-item-store-jaxrs-webapp/resources/items/events).
+
+Deploying the example to another servlet container.
+---------------------------------------------------
+
+> mvn clean package
+
+The command above creates a Servlet 3.x compliant WAR located in the
+target directory. The WAR can be then deployed to your Servlet 3.x
+compliant container.
+
+Running Test Client
+-------------------
+
+After deploying the application into a Servlet 3.x compliant container,
+you can run the attached test by executing:
+
+> mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.external.ExternalTestContainerFactory -Djersey.config.test.container.port=<port>
diff --git a/examples/sse-item-store-jaxrs-webapp/pom.xml b/examples/sse-item-store-jaxrs-webapp/pom.xml
new file mode 100644
index 0000000..afba6c1
--- /dev/null
+++ b/examples/sse-item-store-jaxrs-webapp/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>sse-item-store-jaxrs-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-sse-item-store-jaxrs-webapp</name>
+
+ <description>Jersey JAX-RS 2.1 SSE API-based item store example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-sse</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.connectors</groupId>
+ <artifactId>jersey-apache-connector</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Run the application using "mvn jetty:run" -->
+ <plugin>
+ <!-- TODO unify Jetty in all examples -->
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-maven-plugin</artifactId>
+ <configuration>
+ <scanIntervalSeconds>10</scanIntervalSeconds>
+ <stopPort>9999</stopPort>
+ <stopKey>STOP</stopKey>
+ <webApp>
+ <contextPath>/${project.artifactId}</contextPath>
+ </webApp>
+ <systemProperties>
+ <systemProperty>
+ <name>jetty.port</name>
+ <value>${jersey.config.test.container.port}</value>
+ </systemProperty>
+ </systemProperties>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <properties>
+ <jersey.config.test.container.port>8080</jersey.config.test.container.port>
+ </properties>
+</project>
diff --git a/examples/sse-item-store-jaxrs-webapp/src/main/java/org/glassfish/jersey/examples/sseitemstore/jaxrs/JaxrsItemStoreApp.java b/examples/sse-item-store-jaxrs-webapp/src/main/java/org/glassfish/jersey/examples/sseitemstore/jaxrs/JaxrsItemStoreApp.java
new file mode 100644
index 0000000..ed76ecf
--- /dev/null
+++ b/examples/sse-item-store-jaxrs-webapp/src/main/java/org/glassfish/jersey/examples/sseitemstore/jaxrs/JaxrsItemStoreApp.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sseitemstore.jaxrs;
+
+import javax.ws.rs.ApplicationPath;
+
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * SSE item store JAX-RS application class.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+@ApplicationPath("resources")
+public class JaxrsItemStoreApp extends ResourceConfig {
+ /**
+ * Create new SSE Item Store Example JAX-RS application.
+ */
+ public JaxrsItemStoreApp() {
+ super(JaxrsItemStoreResource.class);
+ }
+}
diff --git a/examples/sse-item-store-jaxrs-webapp/src/main/java/org/glassfish/jersey/examples/sseitemstore/jaxrs/JaxrsItemStoreResource.java b/examples/sse-item-store-jaxrs-webapp/src/main/java/org/glassfish/jersey/examples/sseitemstore/jaxrs/JaxrsItemStoreResource.java
new file mode 100644
index 0000000..a8c193d
--- /dev/null
+++ b/examples/sse-item-store-jaxrs-webapp/src/main/java/org/glassfish/jersey/examples/sseitemstore/jaxrs/JaxrsItemStoreResource.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sseitemstore.jaxrs;
+
+import java.util.LinkedList;
+import java.util.ListIterator;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Logger;
+
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.ServiceUnavailableException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.sse.OutboundSseEvent;
+import javax.ws.rs.sse.Sse;
+import javax.ws.rs.sse.SseBroadcaster;
+import javax.ws.rs.sse.SseEventSink;
+
+import org.glassfish.jersey.media.sse.SseFeature;
+
+
+/**
+ * A resource for storing named items.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("items")
+public class JaxrsItemStoreResource {
+ private static final Logger LOGGER = Logger.getLogger(JaxrsItemStoreResource.class.getName());
+
+ private static final ReentrantReadWriteLock storeLock = new ReentrantReadWriteLock();
+ private static final LinkedList<String> itemStore = new LinkedList<>();
+
+ private static final AtomicReference<SseBroadcaster> BROADCASTER = new AtomicReference<>(null);
+
+ @Context
+ private Sse sse;
+
+ private static volatile long reconnectDelay = 0;
+
+ /**
+ * List all stored items.
+ *
+ * @return list of all stored items.
+ */
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String listItems() {
+ try {
+ storeLock.readLock().lock();
+ return itemStore.toString();
+ } finally {
+ storeLock.readLock().unlock();
+ }
+ }
+
+ /**
+ * Receive & process commands sent by the test client that control the internal resource state.
+ * <p>
+ * Following is the list of recognized commands:
+ * <ul>
+ * <li><b>disconnect</b> - disconnect all registered event streams.</li>
+ * <li><b>reconnect now</b> - enable client reconnecting.</li>
+ * <li><b>reconnect <seconds></b> - disable client reconnecting.
+ * Reconnecting clients will receive a HTTP 503 response with
+ * {@value javax.ws.rs.core.HttpHeaders#RETRY_AFTER} set to the amount of
+ * milliseconds specified.</li>
+ * </ul>
+ *
+ * @param command command to be processed.
+ * @return message about processing result.
+ * @throws BadRequestException in case the command is not recognized or not specified.
+ */
+ @POST
+ @Path("commands")
+ public String processCommand(String command) {
+ if (command == null || command.isEmpty()) {
+ throw new BadRequestException("No command specified.");
+ }
+
+ if ("disconnect".equals(command)) {
+ closeBroadcaster();
+ return "Disconnected.";
+ } else if ("reconnect ".length() < command.length() && command.startsWith("reconnect ")) {
+ final String when = command.substring("reconnect ".length());
+ try {
+ reconnectDelay = "now".equals(when) ? 0 : Long.parseLong(when);
+ return "Reconnect strategy updated: " + when;
+ } catch (NumberFormatException ignore) {
+ // ignored
+ }
+ }
+
+ throw new BadRequestException("Command not recognized: '" + command + "'");
+ }
+
+ /**
+ * TODO - rewrite Connect or re-connect to SSE event stream.
+ *
+ * @param lastEventId Value of custom SSE HTTP <tt>{@value SseFeature#LAST_EVENT_ID_HEADER}</tt> header.
+ * Defaults to {@code -1} if not set.
+ * @throws InternalServerErrorException in case replaying missed events to the reconnected output stream fails.
+ * @throws ServiceUnavailableException in case the reconnect delay is set to a positive value.
+ */
+ @GET
+ @Path("events")
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void itemEvents(@HeaderParam(SseFeature.LAST_EVENT_ID_HEADER) @DefaultValue("-1") int lastEventId,
+ @Context SseEventSink eventSink) {
+
+ if (lastEventId >= 0) {
+ LOGGER.info("Received last event id :" + lastEventId);
+
+ // decide the reconnect handling strategy based on current reconnect delay value.
+ final long delay = reconnectDelay;
+ if (0 < delay) {
+ LOGGER.info("Non-zero reconnect delay [" + delay + "] - responding with HTTP 503.");
+ throw new ServiceUnavailableException(delay);
+ } else {
+ LOGGER.info("Zero reconnect delay - reconnecting.");
+ replayMissedEvents(lastEventId, eventSink);
+ }
+ }
+
+ getBroadcaster().register(eventSink);
+ }
+
+ private void replayMissedEvents(int lastEventId, SseEventSink eventSink) {
+ try {
+ storeLock.readLock().lock();
+ final int firstUnreceived = lastEventId + 1;
+ final int missingCount = itemStore.size() - firstUnreceived;
+ if (missingCount > 0) {
+ LOGGER.info("Replaying events - starting with id " + firstUnreceived);
+ final ListIterator<String> it = itemStore.subList(firstUnreceived, itemStore.size()).listIterator();
+ while (it.hasNext()) {
+ eventSink.send(createItemEvent(it.nextIndex() + firstUnreceived, it.next()));
+ }
+ } else {
+ LOGGER.info("No events to replay.");
+ }
+ } finally {
+ storeLock.readLock().unlock();
+ }
+ }
+
+ /**
+ * Add new item to the item store.
+ * <p>
+ * Invoking this method will fire 2 new SSE events - 1st about newly added item and 2nd about the new item store size.
+ *
+ * @param name item name.
+ */
+ @POST
+ public void addItem(@FormParam("name") String name) {
+ // Ignore if the request was sent without name parameter.
+ if (name == null) {
+ return;
+ }
+
+ final int eventId;
+ try {
+ storeLock.writeLock().lock();
+ eventId = itemStore.size();
+ itemStore.add(name);
+
+ SseBroadcaster sseBroadcaster = getBroadcaster();
+
+ // Broadcasting an un-named event with the name of the newly added item in data
+ sseBroadcaster.broadcast(createItemEvent(eventId, name));
+
+ // Broadcasting a named "size" event with the current size of the items collection in data
+ final OutboundSseEvent event = sse.newEventBuilder().name("size").data(Integer.class, eventId + 1).build();
+ sseBroadcaster.broadcast(event);
+ } finally {
+ storeLock.writeLock().unlock();
+ }
+ }
+
+ private OutboundSseEvent createItemEvent(final int eventId, final String name) {
+ Logger.getLogger(JaxrsItemStoreResource.class.getName())
+ .info("Creating event id [" + eventId + "] name [" + name + "]");
+ return sse.newEventBuilder().id("" + eventId).data(String.class, name).build();
+ }
+
+ /**
+ * Get stored broadcaster or create a new one and store it.
+ *
+ * @return broadcaster instance.
+ */
+ private SseBroadcaster getBroadcaster() {
+ SseBroadcaster sseBroadcaster = BROADCASTER.get();
+ if (sseBroadcaster == null) {
+ BROADCASTER.compareAndSet(null, sse.newBroadcaster());
+ }
+
+ return BROADCASTER.get();
+ }
+
+ /**
+ * Close currently stored broadcaster.
+ */
+ private void closeBroadcaster() {
+ SseBroadcaster sseBroadcaster = BROADCASTER.getAndSet(null);
+ if (sseBroadcaster == null) {
+ return;
+ }
+ sseBroadcaster.close();
+ }
+}
diff --git a/examples/sse-item-store-jaxrs-webapp/src/main/webapp/css/main.css b/examples/sse-item-store-jaxrs-webapp/src/main/webapp/css/main.css
new file mode 100644
index 0000000..5e31320
--- /dev/null
+++ b/examples/sse-item-store-jaxrs-webapp/src/main/webapp/css/main.css
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+div.message {
+ border-radius: 10px;
+ border: thin solid #444444;
+ margin: 10px;
+ width: 300px;
+ padding: 10px;
+ box-shadow: 5px 5px 5px #888888;
+ font-family: Helvetica, serif;
+ color: #333333;
+}
+
+div.items {
+ border-radius: 10px;
+ border: thin dotted #AAAAFF;
+ margin: 10px;
+ padding: 10px;
+ font-family: Helvetica, serif;
+ color: #4444FF;
+}
+
+div.messags {
+ border-radius: 10px;
+ border: thin dotted #888888;
+ margin: 10px;
+ padding: 10px;
+}
diff --git a/examples/sse-item-store-jaxrs-webapp/src/main/webapp/index.html b/examples/sse-item-store-jaxrs-webapp/src/main/webapp/index.html
new file mode 100644
index 0000000..0c488a2
--- /dev/null
+++ b/examples/sse-item-store-jaxrs-webapp/src/main/webapp/index.html
@@ -0,0 +1,27 @@
+<!--
+
+ Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <title>Jersey SSE Item Store Example</title>
+ <link rel="stylesheet" href="css/main.css"/>
+ <script src="js/engine.js" type="text/javascript"></script>
+</head>
+<body>
+<div>
+ Enter item: <input id="name" type="text"/><input value="Add" type="button" onclick="addItem()"/>
+</div>
+<div id="items" class="items"></div>
+<div id="messages" class="messages"></div>
+</body>
+</html>
diff --git a/examples/sse-item-store-jaxrs-webapp/src/main/webapp/js/engine.js b/examples/sse-item-store-jaxrs-webapp/src/main/webapp/js/engine.js
new file mode 100644
index 0000000..3b1f42d
--- /dev/null
+++ b/examples/sse-item-store-jaxrs-webapp/src/main/webapp/js/engine.js
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+"use strict";
+
+function addItem() {
+ var itemInput = document.getElementById("name");
+
+ var req = new XMLHttpRequest();
+ req.open("POST", "resources/items", true);
+ req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+ req.onreadystatechange = function () {
+ if (req.readyState == 4 && req.status == 204) {
+ //Call a function when the state changes.
+ itemInput.value = "";
+ getItems();
+ }
+ };
+ req.send("name=" + itemInput.value);
+}
+
+function getItems() {
+ var req = new XMLHttpRequest();
+ req.open("GET", "resources/items", true);
+ req.setRequestHeader("Accept", "text/plain");
+ req.onreadystatechange = function () {
+ //Call a function when the state changes.
+ if (req.readyState == 4 && req.status == 200) {
+ document.getElementById("items").innerHTML = req.responseText;
+ }
+ };
+ req.send();
+}
+
+function display(data, rgb) {
+ var msgSpan = document.createElement("span");
+ msgSpan.style.color = rgb;
+ msgSpan.innerHTML = data;
+ var msgDiv = document.createElement("div");
+ msgDiv.className = "message";
+ msgDiv.appendChild(msgSpan);
+
+ var messages = document.getElementById("messages");
+ messages.insertBefore(msgDiv, messages.firstChild);
+}
+
+function receiveMessages() {
+ if (typeof(EventSource) !== "undefined") {
+ // Yes! Server-sent events support!
+ var source = new EventSource("resources/items/events");
+ source.onmessage = function (event) {
+ console.log('Received unnamed event: ' + event.data);
+ display("Added new item: " + event.data, "#444444");
+ };
+
+ source.addEventListener("size", function(e) {
+ console.log('Received event ' + event.name + ': ' + event.data);
+ display("New items size: " + event.data, "#0000FF");
+ }, false);
+
+ source.onopen = function (event) {
+ console.log("event source opened");
+ };
+
+ source.onerror = function (event) {
+ console.log('Received error event: ' + event.data);
+ display(event.data, "#FF0000");
+ };
+ } else {
+ // Sorry! No server-sent events support..
+ display('SSE not supported by browser.', "#FF0000");
+ }
+}
+
+window.onload = receiveMessages;
diff --git a/examples/sse-item-store-jaxrs-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jaxrs/JaxrsItemStoreResourceTest.java b/examples/sse-item-store-jaxrs-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jaxrs/JaxrsItemStoreResourceTest.java
new file mode 100644
index 0000000..0f352a6
--- /dev/null
+++ b/examples/sse-item-store-jaxrs-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jaxrs/JaxrsItemStoreResourceTest.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sseitemstore.jaxrs;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.sse.SseEventSource;
+
+import org.glassfish.jersey.apache.connector.ApacheClientProperties;
+import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.external.ExternalTestContainerFactory;
+
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.describedAs;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Item store test.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class JaxrsItemStoreResourceTest extends JerseyTest {
+
+ private static final int RECONNECT_DEFAULT = 500;
+ private static final Logger LOGGER = Logger.getLogger(JaxrsItemStoreResourceTest.class.getName());
+ private static final int MAX_LISTENERS = 5;
+ private static final int MAX_ITEMS = 10;
+
+ private final ExecutorService executorService =
+ Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("JaxrsItemStoreResourceTest-%d").build());
+ private final AtomicReference<Client> client = new AtomicReference<>(null);
+
+ @Override
+ protected Application configure() {
+ return new JaxrsItemStoreApp();
+ }
+
+ protected void configureClient(ClientConfig config) {
+ // using AHC as a test client connector to avoid issues with HttpUrlConnection socket management.
+ PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
+
+ // adjusting max. connections just to be safe - the testEventSourceReconnect is quite greedy...
+ cm.setMaxTotal(MAX_LISTENERS * MAX_ITEMS);
+ cm.setDefaultMaxPerRoute(MAX_LISTENERS * MAX_ITEMS);
+
+ config.property(ApacheClientProperties.CONNECTION_MANAGER, cm)
+ .property(ClientProperties.READ_TIMEOUT, 2000)
+ .connectorProvider(new ApacheConnectorProvider());
+ }
+
+ @Override
+ protected Client getClient() {
+ if (client.get() == null) {
+ ClientConfig clientConfig = new ClientConfig();
+ configureClient(clientConfig);
+ client.compareAndSet(null,
+ ClientBuilder.newBuilder()
+ .withConfig(clientConfig)
+ .executorService(executorService).build());
+ }
+
+ return client.get();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ client.get().close();
+ executorService.shutdown();
+ }
+
+ @Override
+ protected URI getBaseUri() {
+ final UriBuilder baseUriBuilder = UriBuilder.fromUri(super.getBaseUri()).path("sse-item-store-jaxrs-webapp");
+ final boolean externalFactoryInUse = getTestContainerFactory() instanceof ExternalTestContainerFactory;
+ return externalFactoryInUse ? baseUriBuilder.path("resources").build() : baseUriBuilder.build();
+ }
+
+ /**
+ * Test the item addition, addition event broadcasting and item retrieval from {@link JaxrsItemStoreResource}.
+ *
+ * @throws Exception in case of a test failure.
+ */
+ @Test
+ public void testItemsStore() throws Exception {
+ final List<String> items = Collections.unmodifiableList(Arrays.asList("foo", "bar", "baz"));
+ final WebTarget itemsTarget = target("items");
+ final CountDownLatch latch = new CountDownLatch(items.size() * MAX_LISTENERS * 2); // countdown on all events
+ final List<Queue<Integer>> indexQueues = new ArrayList<>(MAX_LISTENERS);
+ final SseEventSource[] sources = new SseEventSource[MAX_LISTENERS];
+ final AtomicInteger sizeEventsCount = new AtomicInteger(0);
+
+ for (int i = 0; i < MAX_LISTENERS; i++) {
+ final int id = i;
+ final SseEventSource es = SseEventSource.target(itemsTarget.path("events")).build();
+ sources[id] = es;
+
+ final Queue<Integer> indexes = new ConcurrentLinkedQueue<>();
+ indexQueues.add(indexes);
+
+ es.register(inboundEvent -> {
+ try {
+ if (null == inboundEvent.getName()) {
+ final String data = inboundEvent.readData();
+ LOGGER.info("[-i-] SOURCE " + id + ": Received event id=" + inboundEvent.getId() + " data=" + data);
+ indexes.add(items.indexOf(data));
+ } else if ("size".equals(inboundEvent.getName())) {
+ sizeEventsCount.incrementAndGet();
+ }
+ } catch (Exception ex) {
+ LOGGER.log(Level.SEVERE, "[-x-] SOURCE " + id + ": Error getting event data.", ex);
+ indexes.add(-999);
+ } finally {
+ latch.countDown();
+ }
+ });
+ }
+
+ try {
+ open(sources);
+ items.forEach((item) -> postItem(itemsTarget, item));
+
+ assertTrue("Waiting to receive all events has timed out.",
+ latch.await((1000 + MAX_LISTENERS * RECONNECT_DEFAULT) * getAsyncTimeoutMultiplier(),
+ TimeUnit.MILLISECONDS));
+
+ // need to force disconnect on server in order for EventSource.close(...) to succeed with HttpUrlConnection
+ sendCommand(itemsTarget, "disconnect");
+ } finally {
+ close(sources);
+ }
+
+ String postedItems = itemsTarget.request().get(String.class);
+ items.forEach((item) -> assertTrue("Item '" + item + "' not stored on server.", postedItems.contains(item)));
+
+ final AtomicInteger queueId = new AtomicInteger(0);
+ indexQueues.forEach((indexes) -> {
+ for (int i = 0; i < items.size(); i++) {
+ assertTrue("Event for '" + items.get(i) + "' not received in queue " + queueId.get(), indexes.contains(i));
+ }
+ assertEquals("Not received the expected number of events in queue " + queueId.get(), items.size(), indexes.size());
+ queueId.incrementAndGet();
+ });
+
+ assertEquals("Number of received 'size' events does not match.", items.size() * MAX_LISTENERS, sizeEventsCount.get());
+ }
+
+ /**
+ * Test the {@link SseEventSource} reconnect feature.
+ *
+ * @throws Exception in case of a test failure.
+ */
+ @Test
+ public void testEventSourceReconnect() throws Exception {
+ final WebTarget itemsTarget = target("items");
+ final CountDownLatch latch = new CountDownLatch(MAX_ITEMS * MAX_LISTENERS * 2); // countdown only on new item events
+ final List<Queue<String>> receivedQueues = new ArrayList<>(MAX_LISTENERS);
+ final SseEventSource[] sources = new SseEventSource[MAX_LISTENERS];
+
+ for (int i = 0; i < MAX_LISTENERS; i++) {
+ final int id = i;
+ final SseEventSource es = SseEventSource.target(itemsTarget.path("events"))
+ .reconnectingEvery(1, TimeUnit.MILLISECONDS).build();
+ sources[id] = es;
+
+ final Queue<String> received = new ConcurrentLinkedQueue<>();
+ receivedQueues.add(received);
+
+ es.register(inboundEvent -> {
+ try {
+ if (null == inboundEvent.getName()) {
+ final String data = inboundEvent.readData();
+ LOGGER.info("[-i-] SOURCE " + id + ": Received event id=" + inboundEvent.getId() + " data=" + data);
+ received.add(data);
+ latch.countDown();
+ }
+ } catch (Exception ex) {
+ LOGGER.log(Level.SEVERE, "[-x-] SOURCE " + id + ": Error getting event data.", ex);
+ received.add("[data processing error]");
+ }
+ });
+ }
+
+ final String[] postedItems = new String[MAX_ITEMS * 2];
+ try {
+ open(sources);
+
+ for (int i = 0; i < MAX_ITEMS; i++) {
+ final String item = String.format("round-1-%02d", i);
+ postItem(itemsTarget, item);
+ postedItems[i] = item;
+ sendCommand(itemsTarget, "disconnect");
+ Thread.sleep(200);
+ }
+
+ final int reconnectDelay = 1;
+ sendCommand(itemsTarget, "reconnect " + reconnectDelay);
+ sendCommand(itemsTarget, "disconnect");
+
+ Thread.sleep(reconnectDelay * 1000);
+
+ for (int i = 0; i < MAX_ITEMS; i++) {
+ final String item = String.format("round-2-%02d", i);
+ postedItems[i + MAX_ITEMS] = item;
+ postItem(itemsTarget, item);
+ }
+
+ sendCommand(itemsTarget, "reconnect now");
+
+ assertTrue("Waiting to receive all events has timed out.",
+ latch.await((1 + MAX_LISTENERS * (MAX_ITEMS + 1) * reconnectDelay) * getAsyncTimeoutMultiplier(),
+ TimeUnit.SECONDS));
+
+ // need to force disconnect on server in order for EventSource.close(...) to succeed with HttpUrlConnection
+ sendCommand(itemsTarget, "disconnect");
+ } finally {
+ close(sources);
+ }
+
+ final String storedItems = itemsTarget.request().get(String.class);
+ for (String item : postedItems) {
+ assertThat("Posted item '" + item + "' stored on server", storedItems, containsString(item));
+ }
+
+ int sourceId = 0;
+ for (Queue<String> queue : receivedQueues) {
+ assertThat("Received events in source " + sourceId, queue,
+ describedAs("Collection containing %0", hasItems(postedItems), Arrays.asList(postedItems).toString()));
+ assertThat("Size of received queue for source " + sourceId, queue.size(), equalTo(postedItems.length));
+ sourceId++;
+ }
+ }
+
+ private static void postItem(final WebTarget itemsTarget, final String item) {
+ final Response response = itemsTarget.request().post(Entity.form(new Form("name", item)));
+ assertEquals("Posting new item has failed.", 204, response.getStatus());
+ LOGGER.info("[-i-] POSTed item: '" + item + "'");
+ }
+
+ private static void open(final SseEventSource[] sources) {
+ Arrays.stream(sources).forEach(SseEventSource::open);
+ }
+
+ private static void close(final SseEventSource[] sources) {
+ int i = 0;
+ for (SseEventSource source : sources) {
+ if (source.isOpen()) {
+ assertTrue("Waiting to close a source has timed out.", source.close(1, TimeUnit.SECONDS));
+// source.close(100, TimeUnit.MILLISECONDS);
+ LOGGER.info("[<--] SOURCE " + i++ + " closed.");
+ }
+ }
+ }
+
+ private static void sendCommand(final WebTarget itemsTarget, final String command) {
+ final Response response = itemsTarget.path("commands").request().post(Entity.text(command));
+ assertEquals("'" + command + "' command has failed.", 200, response.getStatus());
+ LOGGER.info("[-!-] COMMAND '" + command + "' has been processed.");
+ }
+}
diff --git a/examples/sse-item-store-jersey-webapp/README.MD b/examples/sse-item-store-jersey-webapp/README.MD
new file mode 100644
index 0000000..4850875
--- /dev/null
+++ b/examples/sse-item-store-jersey-webapp/README.MD
@@ -0,0 +1,67 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+SSE Item Store Jersey Example
+=============================
+
+An example demonstrating how Server Sent Events (SSE) Jersey support can
+be used to notify clients about changes in server-side managed data
+(collection of items). The example also outlines how SSE events can be
+consumed using javascript browser-based clients.
+
+Contents
+--------
+
+The example consists of a web-based client application and a server-side
+application deployed on a [Jetty servlet container](http://www.eclipse.org/jetty/documentation/current/).
+
+The server side part of the application consists of a JAX-RS resource
+managing collection of string items and a simple HTML page that includes
+a browser SSE client written in Javascript as well as a basic CSS
+stylesheet. The SSE Javascript client connects to the JAX-RS resource
+and transforms the streamed messages into HTML code that is rendered by
+the browser. The javascript client also demonstrates how named and
+unnamed SSE events are handled by HTML5 `EventSource` component. The
+mapping of the URI path space of the server-side part of the application
+is presented in the following table:
+
+URI path | Resource class | HTTP methods
+------------------------------- | ------------------- | --------------
+**_/resources/items_** | ItemStoreResource | GET, POST
+**_/resources/items/events_** | ItemStoreResource | GET (SSE)
+
+Application is configured to run using Jetty maven plugin under base
+path `sse-item-store-webapp`.
+
+Running the Example
+-------------------
+
+> mvn clean compile jetty:run
+
+The command above deploys the current example. After successful
+deployment, you should be able to access the browser SSE client page at
+<http://localhost:8080/sse-item-store-jersey-webapp/index.html>. To see the raw
+SSE event stream, you may also point your browser directly at the
+[`ItemStoreResource`](http://localhost:8080/sse-item-store-jersey-webapp/resources/items/events).
+
+Deploying the example to another servlet container.
+---------------------------------------------------
+
+> mvn clean package
+
+The command above creates a Servlet 3.x compliant WAR located in the
+target directory. The WAR can be then deployed to your Servlet 3.x
+compliant container.
+
+Running Test Client
+-------------------
+
+After deploying the application into a Servlet 3.x compliant container,
+you can run the attached test by executing:
+
+> mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.external.ExternalTestContainerFactory -Djersey.config.test.container.port=<port>
diff --git a/examples/sse-item-store-jersey-webapp/pom.xml b/examples/sse-item-store-jersey-webapp/pom.xml
new file mode 100644
index 0000000..fdadcd5
--- /dev/null
+++ b/examples/sse-item-store-jersey-webapp/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>webapp-example-parent</artifactId>
+ <relativePath>../webapp-example-parent/pom.xml</relativePath>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>sse-item-store-jersey-webapp</artifactId>
+ <packaging>war</packaging>
+ <name>jersey-examples-sse-item-store-jersey-webapp</name>
+
+ <description>Jersey SSE API-based item store example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-sse</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.connectors</groupId>
+ <artifactId>jersey-apache-connector</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Run the application using "mvn jetty:run" -->
+ <plugin>
+ <!-- TODO unify Jetty in all examples -->
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-maven-plugin</artifactId>
+ <configuration>
+ <scanIntervalSeconds>10</scanIntervalSeconds>
+ <stopPort>9999</stopPort>
+ <stopKey>STOP</stopKey>
+ <webApp>
+ <contextPath>/${project.artifactId}</contextPath>
+ </webApp>
+ <systemProperties>
+ <systemProperty>
+ <name>jetty.port</name>
+ <value>${jersey.config.test.container.port}</value>
+ </systemProperty>
+ </systemProperties>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <properties>
+ <jersey.config.test.container.port>8080</jersey.config.test.container.port>
+ </properties>
+</project>
diff --git a/examples/sse-item-store-jersey-webapp/src/main/java/org/glassfish/jersey/examples/sseitemstore/jersey/ItemStoreApp.java b/examples/sse-item-store-jersey-webapp/src/main/java/org/glassfish/jersey/examples/sseitemstore/jersey/ItemStoreApp.java
new file mode 100644
index 0000000..a8ca593
--- /dev/null
+++ b/examples/sse-item-store-jersey-webapp/src/main/java/org/glassfish/jersey/examples/sseitemstore/jersey/ItemStoreApp.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sseitemstore.jersey;
+
+import javax.ws.rs.ApplicationPath;
+
+import org.glassfish.jersey.media.sse.SseFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * SSE item store JAX-RS application class.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@ApplicationPath("resources")
+public class ItemStoreApp extends ResourceConfig {
+ /**
+ * Create new SSE Item Store Example JAX-RS application.
+ */
+ public ItemStoreApp() {
+ super(ItemStoreResource.class, SseFeature.class);
+ }
+}
diff --git a/examples/sse-item-store-jersey-webapp/src/main/java/org/glassfish/jersey/examples/sseitemstore/jersey/ItemStoreResource.java b/examples/sse-item-store-jersey-webapp/src/main/java/org/glassfish/jersey/examples/sseitemstore/jersey/ItemStoreResource.java
new file mode 100644
index 0000000..a8d1dc7
--- /dev/null
+++ b/examples/sse-item-store-jersey-webapp/src/main/java/org/glassfish/jersey/examples/sseitemstore/jersey/ItemStoreResource.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sseitemstore.jersey;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.ListIterator;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.ServiceUnavailableException;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.media.sse.EventOutput;
+import org.glassfish.jersey.media.sse.OutboundEvent;
+import org.glassfish.jersey.media.sse.SseBroadcaster;
+import org.glassfish.jersey.media.sse.SseFeature;
+import org.glassfish.jersey.server.BroadcasterListener;
+import org.glassfish.jersey.server.ChunkedOutput;
+
+/**
+ * A resource for storing named items.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("items")
+public class ItemStoreResource {
+ private static final Logger LOGGER = Logger.getLogger(ItemStoreResource.class.getName());
+
+ private static final ReentrantReadWriteLock storeLock = new ReentrantReadWriteLock();
+ private static final LinkedList<String> itemStore = new LinkedList<String>();
+ private static final SseBroadcaster broadcaster = new SseBroadcaster();
+
+ static {
+ broadcaster.add(new BroadcasterListener<OutboundEvent>() {
+ @Override
+ public void onException(ChunkedOutput<OutboundEvent> chunkedOutput, Exception exception) {
+ LOGGER.log(Level.WARNING,
+ "An exception has been thrown while broadcasting to an event output.",
+ exception);
+ }
+
+ @Override
+ public void onClose(ChunkedOutput<OutboundEvent> chunkedOutput) {
+ LOGGER.log(Level.INFO, "Chunked output has been closed.");
+ }
+ });
+ }
+
+ private static volatile long reconnectDelay = 0;
+
+ /**
+ * List all stored items.
+ *
+ * @return list of all stored items.
+ */
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String listItems() {
+ try {
+ storeLock.readLock().lock();
+ return itemStore.toString();
+ } finally {
+ storeLock.readLock().unlock();
+ }
+ }
+
+ /**
+ * Receive & process commands sent by the test client that control the internal resource state.
+ *
+ * Following is the list of recognized commands:
+ * <ul>
+ * <li><b>disconnect</b> - disconnect all registered event streams.</li>
+ * <li><b>reconnect now</b> - enable client reconnecting.</li>
+ * <li><b>reconnect <seconds></b> - disable client reconnecting.
+ * Reconnecting clients will receive a HTTP 503 response with
+ * {@value javax.ws.rs.core.HttpHeaders#RETRY_AFTER} set to the amount of
+ * milliseconds specified.</li>
+ * </ul>
+ *
+ * @param command command to be processed.
+ * @return message about processing result.
+ * @throws BadRequestException in case the command is not recognized or not specified.
+ */
+ @POST
+ @Path("commands")
+ public String processCommand(String command) {
+ if (command == null || command.isEmpty()) {
+ throw new BadRequestException("No command specified.");
+ }
+
+ if ("disconnect".equals(command)) {
+ broadcaster.closeAll();
+ return "Disconnected.";
+ } else if (command.length() > "reconnect ".length() && command.startsWith("reconnect ")) {
+ final String when = command.substring("reconnect ".length());
+ try {
+ reconnectDelay = "now".equals(when) ? 0 : Long.parseLong(when);
+ return "Reconnect strategy updated: " + when;
+ } catch (NumberFormatException ignore) {
+ // ignored
+ }
+ }
+
+ throw new BadRequestException("Command not recognized: '" + command + "'");
+ }
+
+ /**
+ * Connect or re-connect to SSE event stream.
+ *
+ * @param lastEventId Value of custom SSE HTTP <tt>{@value SseFeature#LAST_EVENT_ID_HEADER}</tt> header.
+ * Defaults to {@code -1} if not set.
+ * @return new SSE event output stream representing the (re-)established SSE client connection.
+ * @throws InternalServerErrorException in case replaying missed events to the reconnected output stream fails.
+ * @throws ServiceUnavailableException in case the reconnect delay is set to a positive value.
+ */
+ @GET
+ @Path("events")
+ @Produces(SseFeature.SERVER_SENT_EVENTS)
+ public EventOutput itemEvents(
+ @HeaderParam(SseFeature.LAST_EVENT_ID_HEADER) @DefaultValue("-1") int lastEventId) {
+ final EventOutput eventOutput = new EventOutput();
+
+ if (lastEventId >= 0) {
+ LOGGER.info("Received last event id :" + lastEventId);
+
+ // decide the reconnect handling strategy based on current reconnect delay value.
+ final long delay = reconnectDelay;
+ if (delay > 0) {
+ LOGGER.info("Non-zero reconnect delay [" + delay + "] - responding with HTTP 503.");
+ throw new ServiceUnavailableException(delay);
+ } else {
+ LOGGER.info("Zero reconnect delay - reconnecting.");
+ replayMissedEvents(lastEventId, eventOutput);
+ }
+ }
+
+ if (!broadcaster.add(eventOutput)) {
+ LOGGER.severe("!!! Unable to add new event output to the broadcaster !!!");
+ // let's try to force a 5s delayed client reconnect attempt
+ throw new ServiceUnavailableException(5L);
+ }
+
+ return eventOutput;
+ }
+
+ private void replayMissedEvents(final int lastEventId, final EventOutput eventOutput) {
+ try {
+ storeLock.readLock().lock();
+ final int firstUnreceived = lastEventId + 1;
+ final int missingCount = itemStore.size() - firstUnreceived;
+ if (missingCount > 0) {
+ LOGGER.info("Replaying events - starting with id " + firstUnreceived);
+ final ListIterator<String> it = itemStore.subList(firstUnreceived, itemStore.size()).listIterator();
+ while (it.hasNext()) {
+ eventOutput.write(createItemEvent(it.nextIndex() + firstUnreceived, it.next()));
+ }
+ } else {
+ LOGGER.info("No events to replay.");
+ }
+ } catch (IOException ex) {
+ throw new InternalServerErrorException("Error replaying missed events", ex);
+ } finally {
+ storeLock.readLock().unlock();
+ }
+ }
+
+ /**
+ * Add new item to the item store.
+ *
+ * Invoking this method will fire 2 new SSE events - 1st about newly added item and 2nd about the new item store size.
+ *
+ * @param name item name.
+ */
+ @POST
+ public void addItem(@FormParam("name") String name) {
+ // Ignore if the request was sent without name parameter.
+ if (name == null) {
+ return;
+ }
+
+ final int eventId;
+ try {
+ storeLock.writeLock().lock();
+ eventId = itemStore.size();
+ itemStore.add(name);
+ // Broadcasting an un-named event with the name of the newly added item in data
+ broadcaster.broadcast(createItemEvent(eventId, name));
+ // Broadcasting a named "size" event with the current size of the items collection in data
+ broadcaster.broadcast(new OutboundEvent.Builder().name("size").data(Integer.class, eventId + 1).build());
+ } finally {
+ storeLock.writeLock().unlock();
+ }
+ }
+
+ private OutboundEvent createItemEvent(final int eventId, final String name) {
+ Logger.getLogger(ItemStoreResource.class.getName()).info("Creating event id [" + eventId + "] name [" + name + "]");
+ return new OutboundEvent.Builder().id("" + eventId).data(String.class, name).build();
+ }
+}
diff --git a/examples/sse-item-store-jersey-webapp/src/main/webapp/css/main.css b/examples/sse-item-store-jersey-webapp/src/main/webapp/css/main.css
new file mode 100644
index 0000000..6907c2c
--- /dev/null
+++ b/examples/sse-item-store-jersey-webapp/src/main/webapp/css/main.css
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+div.message {
+ border-radius: 10px;
+ border: thin solid #444444;
+ margin: 10px;
+ width: 300px;
+ padding: 10px;
+ box-shadow: 5px 5px 5px #888888;
+ font-family: Helvetica, serif;
+ color: #333333;
+}
+
+div.items {
+ border-radius: 10px;
+ border: thin dotted #AAAAFF;
+ margin: 10px;
+ padding: 10px;
+ font-family: Helvetica, serif;
+ color: #4444FF;
+}
+
+div.messags {
+ border-radius: 10px;
+ border: thin dotted #888888;
+ margin: 10px;
+ padding: 10px;
+}
diff --git a/examples/sse-item-store-jersey-webapp/src/main/webapp/index.html b/examples/sse-item-store-jersey-webapp/src/main/webapp/index.html
new file mode 100644
index 0000000..abd98be
--- /dev/null
+++ b/examples/sse-item-store-jersey-webapp/src/main/webapp/index.html
@@ -0,0 +1,27 @@
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <title>Jersey SSE Item Store Example</title>
+ <link rel="stylesheet" href="css/main.css"/>
+ <script src="js/engine.js" type="text/javascript"></script>
+</head>
+<body>
+<div>
+ Enter item: <input id="name" type="text"/><input value="Add" type="button" onclick="addItem()"/>
+</div>
+<div id="items" class="items"></div>
+<div id="messages" class="messages"></div>
+</body>
+</html>
diff --git a/examples/sse-item-store-jersey-webapp/src/main/webapp/js/engine.js b/examples/sse-item-store-jersey-webapp/src/main/webapp/js/engine.js
new file mode 100644
index 0000000..a01776d
--- /dev/null
+++ b/examples/sse-item-store-jersey-webapp/src/main/webapp/js/engine.js
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+"use strict";
+
+function addItem() {
+ var itemInput = document.getElementById("name");
+
+ var req = new XMLHttpRequest();
+ req.open("POST", "resources/items", true);
+ req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+ req.onreadystatechange = function () {
+ if (req.readyState == 4 && req.status == 204) {
+ //Call a function when the state changes.
+ itemInput.value = "";
+ getItems();
+ }
+ };
+ req.send("name=" + itemInput.value);
+}
+
+function getItems() {
+ var req = new XMLHttpRequest();
+ req.open("GET", "resources/items", true);
+ req.setRequestHeader("Accept", "text/plain");
+ req.onreadystatechange = function () {
+ //Call a function when the state changes.
+ if (req.readyState == 4 && req.status == 200) {
+ document.getElementById("items").innerHTML = req.responseText;
+ }
+ };
+ req.send();
+}
+
+function display(data, rgb) {
+ var msgSpan = document.createElement("span");
+ msgSpan.style.color = rgb;
+ msgSpan.innerHTML = data;
+ var msgDiv = document.createElement("div");
+ msgDiv.className = "message";
+ msgDiv.appendChild(msgSpan);
+
+ var messages = document.getElementById("messages");
+ messages.insertBefore(msgDiv, messages.firstChild);
+}
+
+function receiveMessages() {
+ if (typeof(EventSource) !== "undefined") {
+ // Yes! Server-sent events support!
+ var source = new EventSource("resources/items/events");
+ source.onmessage = function (event) {
+ console.log('Received unnamed event: ' + event.data);
+ display("Added new item: " + event.data, "#444444");
+ };
+
+ source.addEventListener("size", function(e) {
+ console.log('Received event ' + event.name + ': ' + event.data);
+ display("New items size: " + event.data, "#0000FF");
+ }, false);
+
+ source.onopen = function (event) {
+ console.log("event source opened");
+ };
+
+ source.onerror = function (event) {
+ console.log('Received error event: ' + event.data);
+ display(event.data, "#FF0000");
+ };
+ } else {
+ // Sorry! No server-sent events support..
+ display('SSE not supported by browser.', "#FF0000");
+ }
+}
+
+window.onload = receiveMessages;
diff --git a/examples/sse-item-store-jersey-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jersey/JerseyItemStoreResourceTest.java b/examples/sse-item-store-jersey-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jersey/JerseyItemStoreResourceTest.java
new file mode 100644
index 0000000..4122a3d
--- /dev/null
+++ b/examples/sse-item-store-jersey-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jersey/JerseyItemStoreResourceTest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sseitemstore.jersey;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.apache.connector.ApacheClientProperties;
+import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.media.sse.EventSource;
+import org.glassfish.jersey.media.sse.SseFeature;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.external.ExternalTestContainerFactory;
+
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.describedAs;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Item store test.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class JerseyItemStoreResourceTest extends JerseyTest {
+
+ private static final Logger LOGGER = Logger.getLogger(JerseyItemStoreResourceTest.class.getName());
+ private static final int MAX_LISTENERS = 5;
+ private static final int MAX_ITEMS = 10;
+
+
+ @Override
+ protected Application configure() {
+ return new ItemStoreApp();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ // using AHC as a test client connector to avoid issues with HttpUrlConnection socket management.
+ PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
+
+ // adjusting max. connections just to be safe - the testEventSourceReconnect is quite greedy...
+ cm.setMaxTotal(MAX_LISTENERS * MAX_ITEMS);
+ cm.setDefaultMaxPerRoute(MAX_LISTENERS * MAX_ITEMS);
+
+ config.register(SseFeature.class)
+ .property(ApacheClientProperties.CONNECTION_MANAGER, cm)
+ .property(ClientProperties.READ_TIMEOUT, 2000)
+ .connectorProvider(new ApacheConnectorProvider());
+ }
+
+ @Override
+ protected URI getBaseUri() {
+ final UriBuilder baseUriBuilder = UriBuilder.fromUri(super.getBaseUri()).path("sse-item-store-jersey-webapp");
+ final boolean externalFactoryInUse = getTestContainerFactory() instanceof ExternalTestContainerFactory;
+ return externalFactoryInUse ? baseUriBuilder.path("resources").build() : baseUriBuilder.build();
+ }
+
+ /**
+ * Test the item addition, addition event broadcasting and item retrieval from {@link ItemStoreResource}.
+ *
+ * @throws Exception in case of a test failure.
+ */
+ @Test
+ public void testItemsStore() throws Exception {
+ final List<String> items = Collections.unmodifiableList(Arrays.asList(
+ "foo",
+ "bar",
+ "baz"));
+ final WebTarget itemsTarget = target("items");
+ final CountDownLatch latch = new CountDownLatch(items.size() * MAX_LISTENERS * 2); // countdown on all events
+ final List<Queue<Integer>> indexQueues = new ArrayList<>(MAX_LISTENERS);
+ final EventSource[] sources = new EventSource[MAX_LISTENERS];
+ final AtomicInteger sizeEventsCount = new AtomicInteger(0);
+
+ for (int i = 0; i < MAX_LISTENERS; i++) {
+ final int id = i;
+ final EventSource es = EventSource.target(itemsTarget.path("events"))
+ .named("SOURCE " + id).build();
+ sources[id] = es;
+
+ final Queue<Integer> indexes = new ConcurrentLinkedQueue<>();
+ indexQueues.add(indexes);
+
+ es.register(inboundEvent -> {
+ try {
+ if (inboundEvent.getName() == null) {
+ final String data = inboundEvent.readData();
+ LOGGER.info("[-i-] SOURCE " + id + ": Received event id=" + inboundEvent.getId() + " data=" + data);
+ indexes.add(items.indexOf(data));
+ } else if ("size".equals(inboundEvent.getName())) {
+ sizeEventsCount.incrementAndGet();
+ }
+ } catch (Exception ex) {
+ LOGGER.log(Level.SEVERE, "[-x-] SOURCE " + id + ": Error getting event data.", ex);
+ indexes.add(-999);
+ } finally {
+ latch.countDown();
+ }
+ });
+ }
+
+ try {
+ open(sources);
+
+ for (String item : items) {
+ postItem(itemsTarget, item);
+ }
+
+ assertTrue("Waiting to receive all events has timed out.",
+ latch.await((1000 + MAX_LISTENERS * EventSource.RECONNECT_DEFAULT) * getAsyncTimeoutMultiplier(),
+ TimeUnit.MILLISECONDS));
+
+ // need to force disconnect on server in order for EventSource.close(...) to succeed with HttpUrlConnection
+ sendCommand(itemsTarget, "disconnect");
+ } finally {
+ close(sources);
+ }
+
+ String postedItems = itemsTarget.request().get(String.class);
+ for (String item : items) {
+ assertTrue("Item '" + item + "' not stored on server.", postedItems.contains(item));
+ }
+
+ int queueId = 0;
+ for (Queue<Integer> indexes : indexQueues) {
+ for (int i = 0; i < items.size(); i++) {
+ assertTrue("Event for '" + items.get(i) + "' not received in queue " + queueId, indexes.contains(i));
+ }
+ assertEquals("Not received the expected number of events in queue " + queueId, items.size(), indexes.size());
+ queueId++;
+ }
+
+ assertEquals("Number of received 'size' events does not match.", items.size() * MAX_LISTENERS, sizeEventsCount.get());
+ }
+
+ /**
+ * Test the {@link EventSource} reconnect feature.
+ *
+ * @throws Exception in case of a test failure.
+ */
+ @Test
+ public void testEventSourceReconnect() throws Exception {
+ final WebTarget itemsTarget = target("items");
+ final CountDownLatch latch = new CountDownLatch(MAX_ITEMS * MAX_LISTENERS * 2); // countdown only on new item events
+ final List<Queue<String>> receivedQueues = new ArrayList<>(MAX_LISTENERS);
+ final EventSource[] sources = new EventSource[MAX_LISTENERS];
+
+ for (int i = 0; i < MAX_LISTENERS; i++) {
+ final int id = i;
+ final EventSource es = EventSource.target(itemsTarget.path("events")).named("SOURCE " + id).build();
+ sources[id] = es;
+
+ final Queue<String> received = new ConcurrentLinkedQueue<>();
+ receivedQueues.add(received);
+
+ es.register(inboundEvent -> {
+ try {
+ if (inboundEvent.getName() == null) {
+ latch.countDown();
+ final String data = inboundEvent.readData();
+ LOGGER.info("[-i-] SOURCE " + id + ": Received event id=" + inboundEvent.getId() + " data=" + data);
+ received.add(data);
+ }
+ } catch (Exception ex) {
+ LOGGER.log(Level.SEVERE, "[-x-] SOURCE " + id + ": Error getting event data.", ex);
+ received.add("[data processing error]");
+ }
+ });
+ }
+
+ final String[] postedItems = new String[MAX_ITEMS * 2];
+ try {
+ open(sources);
+
+ for (int i = 0; i < MAX_ITEMS; i++) {
+ final String item = String.format("round-1-%02d", i);
+ postItem(itemsTarget, item);
+ postedItems[i] = item;
+ sendCommand(itemsTarget, "disconnect");
+ Thread.sleep(100);
+ }
+
+ final int reconnectDelay = 1;
+ sendCommand(itemsTarget, "reconnect " + reconnectDelay);
+ sendCommand(itemsTarget, "disconnect");
+
+ Thread.sleep(reconnectDelay * 1000);
+
+ for (int i = 0; i < MAX_ITEMS; i++) {
+ final String item = String.format("round-2-%02d", i);
+ postedItems[i + MAX_ITEMS] = item;
+ postItem(itemsTarget, item);
+ }
+
+ sendCommand(itemsTarget, "reconnect now");
+
+ assertTrue("Waiting to receive all events has timed out.",
+ latch.await((1 + MAX_LISTENERS * (MAX_ITEMS + 1) * reconnectDelay) * getAsyncTimeoutMultiplier(),
+ TimeUnit.SECONDS));
+
+ // need to force disconnect on server in order for EventSource.close(...) to succeed with HttpUrlConnection
+ sendCommand(itemsTarget, "disconnect");
+ } finally {
+ close(sources);
+ }
+
+ final String storedItems = itemsTarget.request().get(String.class);
+ for (String item : postedItems) {
+ assertThat("Posted item '" + item + "' stored on server", storedItems, containsString(item));
+ }
+
+ int sourceId = 0;
+ for (Queue<String> queue : receivedQueues) {
+ assertThat("Received events in source " + sourceId, queue,
+ describedAs("Collection containing %0", hasItems(postedItems), Arrays.asList(postedItems).toString()));
+ assertThat("Size of received queue for source " + sourceId, queue.size(), equalTo(postedItems.length));
+ sourceId++;
+ }
+ }
+
+ private static void postItem(final WebTarget itemsTarget, final String item) {
+ final Response response = itemsTarget.request().post(Entity.form(new Form("name", item)));
+ assertEquals("Posting new item has failed.", 204, response.getStatus());
+ LOGGER.info("[-i-] POSTed item: '" + item + "'");
+ }
+
+ private static void open(final EventSource[] sources) {
+ int i = 0;
+ for (EventSource source : sources) {
+ source.open();
+ LOGGER.info("[-->] SOURCE " + i++ + " opened.");
+ }
+ }
+
+ private static void close(final EventSource[] sources) {
+ int i = 0;
+ for (EventSource source : sources) {
+ if (source.isOpen()) {
+ assertTrue("Waiting to close a source has timed out.", source.close(1, TimeUnit.SECONDS));
+// source.close(100, TimeUnit.MILLISECONDS);
+ LOGGER.info("[<--] SOURCE " + i++ + " closed.");
+ }
+ }
+ }
+
+ private static void sendCommand(final WebTarget itemsTarget, final String command) {
+ final Response response = itemsTarget.path("commands").request().post(Entity.text(command));
+ assertEquals("'" + command + "' command has failed.", 200, response.getStatus());
+ LOGGER.info("[-!-] COMMAND '" + command + "' has been processed.");
+ }
+}
diff --git a/examples/sse-twitter-aggregator/README.MD b/examples/sse-twitter-aggregator/README.MD
new file mode 100644
index 0000000..45cec61
--- /dev/null
+++ b/examples/sse-twitter-aggregator/README.MD
@@ -0,0 +1,90 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Message Streaming Jersey Example
+================================
+
+An example demonstrating how Server Sent Events (SSE) Jersey support can
+be used to create a message aggregating and streaming resource. The
+example also demonstrates how to consume data provided by a [Twitter
+Streaming API](https://dev.twitter.com/docs/streaming-apis) using Jersey
+`ChunkedInput` client-side support.
+
+Contents
+--------
+
+The example consists of a Swing GUI client application and a server-side
+application deployed on a [Grizzly container](http://grizzly.java.net).
+
+The Swing client application opens in a single window split into
+multiple configuration panels. The top-left panel allows to select which
+aggregators should be executed. It is possible to select a twitter
+message aggregator and/or a fake test message aggregator. The panel
+below provides means for adding new keywords tracked by the Twitter
+message aggregator (if enabled). All actively tracked keywords are
+listed in the right-side list box which supports removal of the keys
+using a `Delete` key on your keyboard. The last panel in the Swing
+client window contains a Start/Stop button for starting or stopping the
+selected aggregators as well as a text area for displaying the messages
+that the aggregators have sent to the message aggregating resource
+deployed on the server-side of the application.
+
+The server side part of the application consists of a message
+aggregating JAX-RS resource and a simple HTML page that includes a
+browser SSE client written in Javascript as well as a basic CSS
+stylesheet. The SSE Javascript client connects to the JAX-RS message
+aggregating resource and transforms the streamed message data into HTML
+code that is rendered by the browser. The mapping of the URI path space
+of the server-side part of the application is presented in the following
+table:
+
+URI path | Resource class | HTTP methods
+---------------------------------- | ----------------------- | --------------
+`/aggregator-api/message/stream` | MessageStreamResource | GET, PUT
+
+Application is programmatically configured to run on Grizzly container
+under base paths `aggregator` and `aggregator-api`.
+
+Running the Example
+-------------------
+
+Before running the example, you need to copy the
+`twitter-api.properties` file available in the root directory of this
+example to your user home directory. Once done, you need to edit the
+file in your user home directory and provide a valid twitter account
+credentials. This step is necessary as the Twitter streaming API is
+protected using HTTP Basic Authentication mechanism and only an
+authenticated twitter account owner can access the API. The
+TwitterAggregator class in the example will use the credentials provided
+in the file to access the Twitter Streaming API.
+
+Once you have set up your Twitter credentials, you're ready to run the
+example. You can run the example by executing a main class via maven as
+follows:
+
+> mvn clean compile exec:java
+
+This deploys the current example as well as it starts the Swing client
+application. You should be able to see the main Swing application window
+open as you run the application as well as you should see the note in
+the command line stating that the Grizzly container has started and the
+server side part of the application has been deployed.
+
+After successful deployment, you should be able to access the browser
+SSE client page at
+
+- <http://localhost:8080/aggregator/index.html>
+
+To see the raw SSE event stream, you may also point your browser
+directly at the
+
+- [MessageStreamResource](http://localhost:8080/aggregator-api/message/stream)
+
+Note that you first need to start the message aggregators via the Swing
+client application in order to be able to receive any SSE events from
+the message streaming resource.
diff --git a/examples/sse-twitter-aggregator/client.cert b/examples/sse-twitter-aggregator/client.cert
new file mode 100644
index 0000000..d43d88b
--- /dev/null
+++ b/examples/sse-twitter-aggregator/client.cert
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIIDJTCCAuOgAwIBAgIET5ZyYjALBgcqhkjOOAQDBQAwdjELMAkGA1UEBhMCQ1oxFzAVBgNVBAgT
+DkN6ZWNoIFJlcHVibGljMQ8wDQYDVQQHEwZQcmFndWUxGzAZBgNVBAoTEk9yYWNsZSBDb3Jwb3Jh
+dGlvbjEPMA0GA1UECxMGSmVyc2V5MQ8wDQYDVQQDEwZDbGllbnQwHhcNMTIwNDI0MDkyOTA2WhcN
+MTIwNzIzMDkyOTA2WjB2MQswCQYDVQQGEwJDWjEXMBUGA1UECBMOQ3plY2ggUmVwdWJsaWMxDzAN
+BgNVBAcTBlByYWd1ZTEbMBkGA1UEChMST3JhY2xlIENvcnBvcmF0aW9uMQ8wDQYDVQQLEwZKZXJz
+ZXkxDzANBgNVBAMTBkNsaWVudDCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu
+7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSf
+n+gEexAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgB
+xwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlXTAs9
+B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6
+ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQBTDv+z0kqA4GEAAKBgBmHNACDk1aw
+vUZjsRecMSBlkkCSqr/cCrYOsNwpfleQKsM6rdOofujANUVeoUFhX8e8K45FknxEqAugmhGQ9NRn
+uMenrvV+XupC0V2uGH0OciXeAzHbfeItBCbmJcvMdPW/q+I2vFchv6+ajEiNHogBrCc3qwSMhyVQ
+ug2fXHmJMAsGByqGSM44BAMFAAMvADAsAhQYznYmH0hrcLni4EqX3Ovac+pNJgIUehnEaW1V5djn
+dhYBAYUkSycETl4=
+-----END CERTIFICATE-----
diff --git a/examples/sse-twitter-aggregator/keystore_client b/examples/sse-twitter-aggregator/keystore_client
new file mode 100644
index 0000000..d016fd2
--- /dev/null
+++ b/examples/sse-twitter-aggregator/keystore_client
Binary files differ
diff --git a/examples/sse-twitter-aggregator/pom.xml b/examples/sse-twitter-aggregator/pom.xml
new file mode 100644
index 0000000..8a65d36
--- /dev/null
+++ b/examples/sse-twitter-aggregator/pom.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>sse-twitter-aggregator</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-sse-twitter-aggregator</name>
+
+ <description>Jersey SSE Twitter Message Aggregator Example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <!-- uncomment to use Grizzly client -->
+ <!--dependency>
+ <groupId>org.glassfish.jersey.connectors</groupId>
+ <artifactId>jersey-grizzly-connector</artifactId>
+ <scope>test</scope>
+ </dependency-->
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-moxy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-sse</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.swinglabs</groupId>
+ <artifactId>swing-layout</artifactId>
+ <version>1.0.3</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.aggregator.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/AbstractTestAggregator.java b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/AbstractTestAggregator.java
new file mode 100644
index 0000000..3c4f1e9
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/AbstractTestAggregator.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.aggregator;
+
+import java.util.Random;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.moxy.json.MoxyJsonFeature;
+
+/**
+ * Fake message aggregator used for testing purposes.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public abstract class AbstractTestAggregator implements DataAggregator {
+ private static final Logger LOGGER = Logger.getLogger(AbstractTestAggregator.class.getName());
+ private static final String[] MESSAGES = new String[] {
+ "Where do your RESTful Web Services want to go today?",
+ "Jersey RESTful Web Services framework rocks!",
+ "Jersey and JAX-RS are cool!",
+ "What are the 5 insane but true things about JAX-RS?",
+ "Wow, JAX-RS 2.0 provides asynchronous service and client APIs!",
+ "Finally! JAX-RS 2.0 adds filters and interceptors support.",
+ "Jersey 2.0 programmatic resource API looks great!",
+ "How could I live without Jersey ResourceConfig class??",
+ "Just wrote my first JAX-RS service using Jersey.",
+ "Jersey is the best RESTful framework ever.",
+ "JAX-RS rules the web services.",
+ "Jersey 2.0 is the new American idol!"
+ };
+ private static final String IMG_URI
+ = "http://files.softicons.com/download/internet-cons/halloween-avatars-icons-by-deleket/png/48/Voodoo%20Doll.png";
+
+ private final String rgbColor;
+ private volatile boolean running;
+
+ AbstractTestAggregator(String rgbColor) {
+ this.rgbColor = rgbColor;
+ }
+
+ @Override
+ public void start(final String keywords, final DataListener msgListener) {
+ msgListener.onStart();
+ running = true;
+
+ final Random rnd = new Random();
+ final String aggregatorPrefix = getPrefix();
+
+ Executors.newSingleThreadExecutor().submit(() -> {
+ final Client resourceClient = ClientBuilder.newClient();
+ resourceClient.register(new MoxyJsonFeature());
+ final WebTarget messageStreamResource = resourceClient.target(App.getApiUri()).path(getPath());
+
+ try {
+ while (running) {
+ final Message message = new Message(
+ aggregatorPrefix + " " + MESSAGES[rnd.nextInt(MESSAGES.length)],
+ rgbColor,
+ IMG_URI);
+ msgListener.onMessage(message);
+ final Response r = messageStreamResource.request().put(Entity.json(message));
+ if (r.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) {
+ LOGGER.warning("Unexpected PUT message response status code: " + r.getStatus());
+ }
+ Thread.sleep(rnd.nextInt(1000) + 750);
+ }
+ msgListener.onComplete();
+ } catch (Throwable t) {
+ LOGGER.log(Level.WARNING, "Waiting for a message has been interrupted.", t);
+ msgListener.onError();
+ }
+ });
+ }
+
+
+ @Override
+ public void stop() {
+ running = false;
+ }
+
+ /**
+ * Get relative path to the event stream.
+ */
+ protected abstract String getPath();
+
+ /**
+ * Get message prefix to identify the concrete aggregator.
+ *
+ * @return message prefix (aggregator qualifier)
+ */
+ protected abstract String getPrefix();
+
+}
diff --git a/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/App.java b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/App.java
new file mode 100644
index 0000000..70f928a
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/App.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.aggregator;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.ext.RuntimeDelegate;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer;
+import org.glassfish.jersey.media.sse.SseFeature;
+import org.glassfish.jersey.message.internal.ReaderWriter;
+import org.glassfish.jersey.moxy.json.MoxyJsonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpHandler;
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.grizzly.http.server.NetworkListener;
+import org.glassfish.grizzly.http.server.Request;
+import org.glassfish.grizzly.http.server.Response;
+import org.glassfish.grizzly.http.server.ServerConfiguration;
+import org.glassfish.grizzly.http.util.HttpStatus;
+
+/**
+ * Jersey Twitter Aggregator example application.
+ */
+public class App {
+
+ private static final Properties TWITTER_PROPERTIES = loadSettings();
+ private static final String TWITTER_USER_NAME = "twitter.user.name";
+ private static final String TWITTER_USER_PASSWORD = "twitter.user.password";
+ private static final String TWITTER_PROPERTIES_FILE_NAME = "twitter-api.properties";
+
+ private static final String APP_PATH = "/aggregator/";
+ private static final String API_PATH = "/aggregator-api/";
+ static final String WEB_ROOT = "/webroot";
+ private static final int PORT = 8080;
+
+ /**
+ * Starts Grizzly HTTP server exposing static content, JAX-RS resources
+ * and web sockets defined in this application.
+ *
+ * @param webRootPath static content root path.
+ */
+ private static void startServer(String webRootPath) {
+ final HttpServer server = new HttpServer();
+ Runtime.getRuntime().addShutdownHook(new Thread(server::shutdownNow));
+
+ final NetworkListener listener = new NetworkListener("grizzly", "localhost", PORT);
+
+ server.addListener(listener);
+
+ final ServerConfiguration config = server.getServerConfiguration();
+ // add handler for serving static content
+ config.addHttpHandler(new StaticContentHandler(webRootPath),
+ APP_PATH);
+
+ // add handler for serving JAX-RS resources
+ config.addHttpHandler(RuntimeDelegate.getInstance().createEndpoint(createResourceConfig(), GrizzlyHttpContainer.class),
+ API_PATH);
+
+ try {
+ // Start the server.
+ server.start();
+ } catch (Exception ex) {
+ throw new ProcessingException("Exception thrown when trying to start grizzly server", ex);
+ }
+ }
+
+ public static void main(String[] args) {
+ MainWindow.main(args);
+
+ try {
+ System.out.println("\"SSE Twitter Message Aggregator\" Jersey Example App");
+ startServer(args.length >= 1 ? args[0] : null);
+ System.out.println(String.format("Application started.\n"
+ + "Access it at %s\n"
+ + "Stop the application using CTRL+C",
+ getAppUri()));
+
+ Thread.currentThread().join();
+ } catch (InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ static String getApiUri() {
+ return String.format("http://localhost:%s%s", PORT, API_PATH);
+ }
+
+ private static String getAppUri() {
+ return String.format("http://localhost:%s%s", PORT, APP_PATH);
+ }
+
+ /**
+ * Create Jersey server-side application resource configuration.
+ *
+ * @return Jersey server-side application configuration.
+ */
+ private static ResourceConfig createResourceConfig() {
+ return new ResourceConfig()
+ .registerClasses(MessageStreamResourceJersey.class,
+ MessageStreamResourceJaxRs.class,
+ SseFeature.class,
+ MoxyJsonFeature.class);
+ }
+
+ /**
+ * Get configured twitter user name.
+ *
+ * @return configured twitter user name.
+ */
+ static String getTwitterUserName() {
+ return (String) TWITTER_PROPERTIES.get(TWITTER_USER_NAME);
+ }
+
+ /**
+ * Get configured twitter user password.
+ *
+ * @return configured twitter user password.
+ */
+ static String getTwitterUserPassword() {
+ return (String) TWITTER_PROPERTIES.get(TWITTER_USER_PASSWORD);
+ }
+
+ private static Properties loadSettings() {
+ final Properties properties = new Properties();
+
+ FileInputStream st = null;
+ try {
+ String homeDir = System.getProperty("user.home");
+ st = new FileInputStream(homeDir + File.separator + TWITTER_PROPERTIES_FILE_NAME);
+ properties.load(st);
+ } catch (IOException e) {
+ // ignore
+ } finally {
+ if (st != null) {
+ try {
+ st.close();
+ } catch (IOException ex) {
+ // ignore
+ }
+ }
+ }
+
+ for (String name : new String[] {TWITTER_USER_NAME, TWITTER_USER_PASSWORD}) {
+ String value = System.getProperty(name);
+ if (value != null) {
+ properties.setProperty(name, value);
+ }
+ }
+
+ if (properties.getProperty(TWITTER_USER_NAME) == null
+ || properties.getProperty(TWITTER_USER_PASSWORD) == null) {
+ System.out.println(String.format(
+ "'%s' and '%s' properties not set. "
+ + "You need to provide them either via '$HOME/%s' file or as system properties.",
+ TWITTER_USER_NAME, TWITTER_USER_PASSWORD, TWITTER_PROPERTIES_FILE_NAME));
+ System.exit(1);
+ }
+ return properties;
+ }
+
+ /**
+ * Simple HttpHandler for serving static content included in web root
+ * directory of this application.
+ */
+ private static class StaticContentHandler extends HttpHandler {
+
+ private static final HashMap<String, String> EXTENSION_TO_MEDIA_TYPE;
+
+ static {
+ EXTENSION_TO_MEDIA_TYPE = new HashMap<>();
+
+ EXTENSION_TO_MEDIA_TYPE.put("html", "text/html");
+ EXTENSION_TO_MEDIA_TYPE.put("js", "application/javascript");
+ EXTENSION_TO_MEDIA_TYPE.put("css", "text/css");
+ EXTENSION_TO_MEDIA_TYPE.put("png", "image/png");
+ EXTENSION_TO_MEDIA_TYPE.put("ico", "image/png");
+ }
+
+ private final String webRootPath;
+
+ StaticContentHandler(String webRootPath) {
+ this.webRootPath = webRootPath;
+ }
+
+ @Override
+ public void service(Request request, Response response) throws Exception {
+ String uri = request.getRequestURI();
+
+ int pos = uri.lastIndexOf('.');
+ String extension = uri.substring(pos + 1);
+ String mediaType = EXTENSION_TO_MEDIA_TYPE.get(extension);
+
+ if (uri.contains("..") || mediaType == null) {
+ response.sendError(HttpStatus.NOT_FOUND_404.getStatusCode());
+ return;
+ }
+
+ final String resourcesContextPath = request.getContextPath();
+ System.out.println("context: " + resourcesContextPath);
+ if (resourcesContextPath != null && !resourcesContextPath.isEmpty()) {
+ if (!uri.startsWith(resourcesContextPath)) {
+ response.sendError(HttpStatus.NOT_FOUND_404.getStatusCode());
+ return;
+ }
+
+ uri = uri.substring(resourcesContextPath.length());
+ System.out.println("URI: " + uri);
+ }
+
+ InputStream fileStream;
+
+ try {
+ fileStream = webRootPath == null
+ ? App.class.getResourceAsStream(WEB_ROOT + uri)
+ : new FileInputStream(webRootPath + uri);
+ } catch (IOException e) {
+ fileStream = null;
+ }
+
+ if (fileStream == null) {
+ response.sendError(HttpStatus.NOT_FOUND_404.getStatusCode());
+ } else {
+ response.setStatus(HttpStatus.OK_200);
+ response.setContentType(mediaType);
+ ReaderWriter.writeTo(fileStream, response.getOutputStream());
+ }
+ }
+ }
+}
diff --git a/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/DataAggregator.java b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/DataAggregator.java
new file mode 100644
index 0000000..6c677ae
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/DataAggregator.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.aggregator;
+
+/**
+ * Data aggregator for listening for events aggregated based on give keywords.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public interface DataAggregator {
+ void start(String keywords, DataListener msgListener);
+
+ void stop();
+}
diff --git a/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/DataListener.java b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/DataListener.java
new file mode 100644
index 0000000..5b513d7
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/DataListener.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.aggregator;
+
+/**
+ * Incoming data listener.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public interface DataListener {
+ /**
+ * Invoked when the connection to the data stream has been established.
+ */
+ public void onStart();
+
+ /**
+ * Invoked when the data stream has dried out (or the connection has been closed).
+ */
+ public void onComplete();
+
+ /**
+ * Invoked when there was an error while receiving streamed data.
+ */
+ public void onError();
+
+ /**
+ * Invoked when a new message data are available.
+ *
+ * @param message new message data.
+ */
+ public void onMessage(Message message);
+}
diff --git a/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/MainWindow.form b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/MainWindow.form
new file mode 100644
index 0000000..73beb7e
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/MainWindow.form
@@ -0,0 +1,342 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<Form version="1.3" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
+ <Properties>
+ <Property name="defaultCloseOperation" type="int" value="3"/>
+ <Property name="title" type="java.lang.String" value="Message aggregator"/>
+ <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[304, 300]"/>
+ </Property>
+ </Properties>
+ <SyntheticProperties>
+ <SyntheticProperty name="formSizePolicy" type="int" value="1"/>
+ </SyntheticProperties>
+ <AuxValues>
+ <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="2"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ </AuxValues>
+
+ <Layout>
+ <DimensionLayout dim="0">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="0" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Component id="jPanel1" max="32767" attributes="0"/>
+ <Component id="jPanel4" alignment="0" max="32767" attributes="0"/>
+ <Component id="jPanel3" alignment="0" max="32767" attributes="0"/>
+ </Group>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="jPanel2" max="32767" attributes="0"/>
+ <EmptySpace min="-2" max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ <DimensionLayout dim="1">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="0" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Component id="jPanel2" max="32767" attributes="0"/>
+ <Group type="102" attributes="0">
+ <Component id="jPanel4" min="-2" max="-2" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="jPanel3" min="-2" max="-2" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="jPanel1" max="32767" attributes="0"/>
+ </Group>
+ </Group>
+ <EmptySpace max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ </Layout>
+ <SubComponents>
+ <Container class="javax.swing.JPanel" name="jPanel3">
+ <Properties>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
+ <EtchetBorder/>
+ </Border>
+ </Property>
+ </Properties>
+ <AuxValues>
+ <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
+ <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
+ </AuxValues>
+
+ <Layout>
+ <DimensionLayout dim="0">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="0" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="newKeywordField" max="32767" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="addButton" min="-2" pref="72" max="-2" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ <DimensionLayout dim="1">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="0" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="3" attributes="0">
+ <Component id="newKeywordField" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="addButton" alignment="3" min="-2" max="-2" attributes="0"/>
+ </Group>
+ <EmptySpace max="32767" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JTextField" name="newKeywordField">
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="onKeywordEntered"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JButton" name="addButton">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Add"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="onAdd"/>
+ </Events>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="jPanel1">
+ <Properties>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
+ <EtchetBorder/>
+ </Border>
+ </Property>
+ </Properties>
+ <AuxValues>
+ <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
+ <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
+ </AuxValues>
+
+ <Layout>
+ <DimensionLayout dim="0">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="0" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" attributes="0">
+ <Component id="jScrollPane2" pref="247" max="32767" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ </Group>
+ <Group type="102" attributes="0">
+ <Component id="connectionStatusLabel" max="32767" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="startStopButton" min="-2" max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ <DimensionLayout dim="1">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="0" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="3" attributes="0">
+ <Component id="startStopButton" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="connectionStatusLabel" alignment="3" min="-2" max="-2" attributes="0"/>
+ </Group>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="jScrollPane2" max="32767" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="connectionStatusLabel">
+ </Component>
+ <Component class="javax.swing.JButton" name="startStopButton">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Start"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="onStartOrStop"/>
+ </Events>
+ </Component>
+ <Container class="javax.swing.JScrollPane" name="jScrollPane2">
+ <AuxValues>
+ <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
+ <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
+ <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JTextArea" name="messagesArea">
+ <Properties>
+ <Property name="columns" type="int" value="20"/>
+ <Property name="lineWrap" type="boolean" value="true"/>
+ <Property name="rows" type="int" value="5"/>
+ <Property name="wrapStyleWord" type="boolean" value="true"/>
+ <Property name="focusable" type="boolean" value="false"/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="jPanel2">
+ <Properties>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
+ <EtchetBorder/>
+ </Border>
+ </Property>
+ </Properties>
+ <AuxValues>
+ <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
+ <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
+ </AuxValues>
+
+ <Layout>
+ <DimensionLayout dim="0">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="0" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="jScrollPane1" pref="97" max="32767" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ <DimensionLayout dim="1">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="0" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="jScrollPane1" pref="332" max="32767" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ </Layout>
+ <SubComponents>
+ <Container class="javax.swing.JScrollPane" name="jScrollPane1">
+ <AuxValues>
+ <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
+ <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
+ <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JList" name="keywordList">
+ <Properties>
+ <Property name="model" type="javax.swing.ListModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
+ <Connection code="keywordListModel" type="code"/>
+ </Property>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="jPanel4">
+ <Properties>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
+ <EtchetBorder/>
+ </Border>
+ </Property>
+ </Properties>
+
+ <Layout>
+ <DimensionLayout dim="0">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="1" attributes="0">
+ <Component id="twitterColorLabel" min="-2" max="-2" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="twitterCheckbox" max="32767" attributes="0"/>
+ </Group>
+ <Group type="102" alignment="1" attributes="0">
+ <Component id="testColorLabel" min="-2" max="-2" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="testCheckbox" max="32767" attributes="0"/>
+ </Group>
+ </Group>
+ <EmptySpace max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ <DimensionLayout dim="1">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="0" attributes="0">
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="3" attributes="0">
+ <Component id="twitterCheckbox" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="twitterColorLabel" alignment="3" min="-2" max="-2" attributes="0"/>
+ </Group>
+ <EmptySpace max="-2" attributes="0"/>
+ <Group type="103" groupAlignment="3" attributes="0">
+ <Component id="testCheckbox" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="testColorLabel" alignment="3" min="-2" max="-2" attributes="0"/>
+ </Group>
+ <EmptySpace max="32767" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JCheckBox" name="twitterCheckbox">
+ <Properties>
+ <Property name="selected" type="boolean" value="true"/>
+ <Property name="text" type="java.lang.String" value="Twitter aggregator"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JLabel" name="twitterColorLabel">
+ <Properties>
+ <Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
+ <Color blue="0" green="aa" red="55" type="rgb"/>
+ </Property>
+ <Property name="text" type="java.lang.String" value=" "/>
+ <Property name="opaque" type="boolean" value="true"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JLabel" name="testColorLabel">
+ <Properties>
+ <Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
+ <Color blue="aa" green="55" red="0" type="rgb"/>
+ </Property>
+ <Property name="text" type="java.lang.String" value=" "/>
+ <Property name="opaque" type="boolean" value="true"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JCheckBox" name="testCheckbox">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Test Aggregator"/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/MainWindow.java b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/MainWindow.java
new file mode 100644
index 0000000..608c288
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/MainWindow.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.aggregator;
+
+import java.awt.Color;
+import java.awt.EventQueue;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.swing.ActionMap;
+import javax.swing.DefaultListModel;
+import javax.swing.InputMap;
+import javax.swing.JComponent;
+import javax.swing.KeyStroke;
+import javax.swing.WindowConstants;
+
+/**
+ * Main data aggregator client application UI window.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class MainWindow extends javax.swing.JFrame {
+
+ private final DefaultListModel keywordListModel;
+ private final AtomicBoolean receiveMessages;
+ private final List<DataAggregator> dataAggregators;
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton addButton;
+ private javax.swing.JLabel connectionStatusLabel;
+ private javax.swing.JPanel jPanel4;
+ private javax.swing.JList keywordList;
+ private javax.swing.JTextArea messagesArea;
+ private javax.swing.JTextField newKeywordField;
+ private javax.swing.JButton startStopButton;
+ private javax.swing.JCheckBox testCheckbox;
+ private javax.swing.JLabel testColorLabel;
+ private javax.swing.JCheckBox twitterCheckbox;
+ private javax.swing.JLabel twitterColorLabel;
+ // End of variables declaration//GEN-END:variables
+
+ /**
+ * Creates new form MessageAggregator
+ */
+ public MainWindow() {
+ keywordListModel = new DefaultListModel();
+ receiveMessages = new AtomicBoolean(false);
+
+ initComponents();
+
+ // Bind "remove selected list items" action to DELETE key pressed in keyword list.
+ ActionMap actionMap = keywordList.getActionMap();
+ InputMap inputMap = keywordList.getInputMap(JComponent.WHEN_FOCUSED);
+
+ final String actionKey = "RemoveSelectedListItems";
+ actionMap.put(actionKey, new RemoveSelectedListItemsAction(keywordList, keywordListModel));
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), actionKey);
+
+ dataAggregators = new ArrayList<DataAggregator>(2);
+ }
+
+ private void initAggregators() {
+ if (twitterCheckbox.isSelected()) {
+ dataAggregators.add(new TwitterAggregator(colorToString(twitterColorLabel.getBackground())));
+ }
+ if (testCheckbox.isSelected()) {
+ dataAggregators.add(new TestAggregatorJersey(colorToString(testColorLabel.getBackground())));
+ dataAggregators.add(new TestAggregatorJaxRs(colorToString(testColorLabel.getBackground())));
+ }
+ }
+
+ private String colorToString(Color color) {
+ StringBuilder colorBuilder = new StringBuilder();
+
+ colorBuilder
+ .append(String.format("%02X", color.getRed()))
+ .append(String.format("%02X", color.getGreen()))
+ .append(String.format("%02X", color.getBlue()));
+
+ return colorBuilder.toString();
+ }
+
+ private void onKeywordEntered(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_onKeywordEntered
+ final String keyword = newKeywordField.getText();
+ if (keyword == null || keyword.isEmpty() || keywordListModel.contains(keyword)) {
+ return;
+ }
+ addButton.doClick();
+ }//GEN-LAST:event_onKeywordEntered
+
+ private void onAdd(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_onAdd
+ final String keyword = newKeywordField.getText();
+ if (keyword == null || keyword.isEmpty() || keywordListModel.contains(keyword)) {
+ return;
+ }
+
+ keywordListModel.addElement(keyword);
+ newKeywordField.setText("");
+ }//GEN-LAST:event_onAdd
+
+ private void onStartOrStop(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_onStartOrStop
+ if (receiveMessages.get()) {
+ // stop
+ receiveMessages.set(false);
+ startStopButton.setText("Start");
+ newKeywordField.setEnabled(true);
+ addButton.setEnabled(true);
+ stop();
+ } else {
+ // start
+ receiveMessages.set(true);
+ startStopButton.setText("Stop");
+ connectionStatusLabel.setText("Connecting...");
+ newKeywordField.setEnabled(false);
+ addButton.setEnabled(false);
+ messagesArea.setText("");
+
+ StringBuilder keywordsBuilder = new StringBuilder();
+ final Enumeration<?> elements = keywordListModel.elements();
+ while (elements.hasMoreElements()) {
+ keywordsBuilder.append(elements.nextElement());
+ if (elements.hasMoreElements()) {
+ keywordsBuilder.append(",");
+ }
+ }
+
+ start(keywordsBuilder.toString(), new DataListener() {
+ @Override
+ public void onStart() {
+ EventQueue.invokeLater(() -> connectionStatusLabel.setText("Connected."));
+ }
+
+ @Override
+ public void onMessage(final Message message) {
+ EventQueue.invokeLater(() -> messagesArea.append(message.getText() + "\n\n"));
+ }
+
+ @Override
+ public void onError() {
+ EventQueue.invokeLater(() -> {
+ connectionStatusLabel.setText("Connection Error!");
+ receiveMessages.set(false);
+ startStopButton.setText("Start");
+ newKeywordField.setEnabled(true);
+ addButton.setEnabled(true);
+ });
+ }
+
+ @Override
+ public void onComplete() {
+ EventQueue.invokeLater(() -> connectionStatusLabel.setText("Disconnected."));
+ }
+ });
+ }
+
+ }//GEN-LAST:event_onStartOrStop
+
+ private void stop() {
+ for (DataAggregator dataAggregator : dataAggregators) {
+ dataAggregator.stop();
+ }
+ dataAggregators.clear();
+ }
+
+ private void start(String keywords, DataListener listener) {
+ initAggregators();
+
+ for (DataAggregator dataAggregator : dataAggregators) {
+ dataAggregator.start(keywords, listener);
+ }
+ }
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String args[]) {
+ /* Create and display the form */
+ EventQueue.invokeLater(() -> {
+ final MainWindow messageAggregator = new MainWindow();
+ messageAggregator.setLocationRelativeTo(null);
+ messageAggregator.setVisible(true);
+ });
+ }
+
+ /**
+ * This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ javax.swing.JPanel jPanel3 = new javax.swing.JPanel();
+ newKeywordField = new javax.swing.JTextField();
+ addButton = new javax.swing.JButton();
+ javax.swing.JPanel jPanel1 = new javax.swing.JPanel();
+ connectionStatusLabel = new javax.swing.JLabel();
+ startStopButton = new javax.swing.JButton();
+ javax.swing.JScrollPane jScrollPane2 = new javax.swing.JScrollPane();
+ messagesArea = new javax.swing.JTextArea();
+ javax.swing.JPanel jPanel2 = new javax.swing.JPanel();
+ javax.swing.JScrollPane jScrollPane1 = new javax.swing.JScrollPane();
+ keywordList = new javax.swing.JList();
+ jPanel4 = new javax.swing.JPanel();
+ twitterCheckbox = new javax.swing.JCheckBox();
+ twitterColorLabel = new javax.swing.JLabel();
+ testColorLabel = new javax.swing.JLabel();
+ testCheckbox = new javax.swing.JCheckBox();
+
+ setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ setTitle("Message aggregator");
+ setMinimumSize(new java.awt.Dimension(304, 300));
+
+ jPanel3.setBorder(javax.swing.BorderFactory.createEtchedBorder());
+
+ newKeywordField.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ onKeywordEntered(evt);
+ }
+ });
+
+ addButton.setText("Add");
+ addButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ onAdd(evt);
+ }
+ });
+
+ org.jdesktop.layout.GroupLayout jPanel3Layout = new org.jdesktop.layout.GroupLayout(jPanel3);
+ jPanel3.setLayout(jPanel3Layout);
+ jPanel3Layout.setHorizontalGroup(
+ jPanel3Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel3Layout.createSequentialGroup()
+ .addContainerGap()
+ .add(newKeywordField)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(addButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 72,
+ org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
+ .addContainerGap())
+ );
+ jPanel3Layout.setVerticalGroup(
+ jPanel3Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel3Layout.createSequentialGroup()
+ .addContainerGap()
+ .add(jPanel3Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
+ .add(newKeywordField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
+ .add(addButton))
+ .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+
+ jPanel1.setBorder(javax.swing.BorderFactory.createEtchedBorder());
+
+ startStopButton.setText("Start");
+ startStopButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ onStartOrStop(evt);
+ }
+ });
+
+ messagesArea.setColumns(20);
+ messagesArea.setLineWrap(true);
+ messagesArea.setRows(5);
+ messagesArea.setWrapStyleWord(true);
+ messagesArea.setFocusable(false);
+ jScrollPane2.setViewportView(messagesArea);
+
+ org.jdesktop.layout.GroupLayout jPanel1Layout = new org.jdesktop.layout.GroupLayout(jPanel1);
+ jPanel1.setLayout(jPanel1Layout);
+ jPanel1Layout.setHorizontalGroup(
+ jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel1Layout.createSequentialGroup()
+ .addContainerGap()
+ .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel1Layout.createSequentialGroup()
+ .add(jScrollPane2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 247,
+ Short.MAX_VALUE)
+ .addContainerGap())
+ .add(jPanel1Layout.createSequentialGroup()
+ .add(connectionStatusLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(startStopButton))))
+ );
+ jPanel1Layout.setVerticalGroup(
+ jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel1Layout.createSequentialGroup()
+ .addContainerGap()
+ .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
+ .add(startStopButton)
+ .add(connectionStatusLabel))
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(jScrollPane2)
+ .addContainerGap())
+ );
+
+ jPanel2.setBorder(javax.swing.BorderFactory.createEtchedBorder());
+
+ keywordList.setModel(keywordListModel);
+ jScrollPane1.setViewportView(keywordList);
+
+ org.jdesktop.layout.GroupLayout jPanel2Layout = new org.jdesktop.layout.GroupLayout(jPanel2);
+ jPanel2.setLayout(jPanel2Layout);
+ jPanel2Layout.setHorizontalGroup(
+ jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel2Layout.createSequentialGroup()
+ .addContainerGap()
+ .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 97, Short.MAX_VALUE)
+ .addContainerGap())
+ );
+ jPanel2Layout.setVerticalGroup(
+ jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel2Layout.createSequentialGroup()
+ .addContainerGap()
+ .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 332, Short.MAX_VALUE)
+ .addContainerGap())
+ );
+
+ jPanel4.setBorder(javax.swing.BorderFactory.createEtchedBorder());
+
+ twitterCheckbox.setSelected(true);
+ twitterCheckbox.setText("Twitter aggregator");
+
+ twitterColorLabel.setBackground(new java.awt.Color(85, 170, 0));
+ twitterColorLabel.setText(" ");
+ twitterColorLabel.setOpaque(true);
+
+ testColorLabel.setBackground(new java.awt.Color(0, 85, 170));
+ testColorLabel.setText(" ");
+ testColorLabel.setOpaque(true);
+
+ testCheckbox.setText("Test Aggregator");
+
+ org.jdesktop.layout.GroupLayout jPanel4Layout = new org.jdesktop.layout.GroupLayout(jPanel4);
+ jPanel4.setLayout(jPanel4Layout);
+ jPanel4Layout.setHorizontalGroup(
+ jPanel4Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel4Layout.createSequentialGroup()
+ .addContainerGap()
+ .add(jPanel4Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(org.jdesktop.layout.GroupLayout.TRAILING, jPanel4Layout.createSequentialGroup()
+ .add(twitterColorLabel)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(twitterCheckbox, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .add(org.jdesktop.layout.GroupLayout.TRAILING, jPanel4Layout.createSequentialGroup()
+ .add(testColorLabel)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(testCheckbox, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
+ .addContainerGap())
+ );
+ jPanel4Layout.setVerticalGroup(
+ jPanel4Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel4Layout.createSequentialGroup()
+ .addContainerGap()
+ .add(jPanel4Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
+ .add(twitterCheckbox)
+ .add(twitterColorLabel))
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(jPanel4Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
+ .add(testCheckbox)
+ .add(testColorLabel))
+ .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+
+ org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(layout.createSequentialGroup()
+ .addContainerGap()
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .add(jPanel4, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .add(jPanel3, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(jPanel2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(layout.createSequentialGroup()
+ .addContainerGap()
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .add(layout.createSequentialGroup()
+ .add(jPanel4, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(jPanel3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(jPanel1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
+ org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
+ .addContainerGap())
+ );
+
+ pack();
+ }// </editor-fold>//GEN-END:initComponents
+}
diff --git a/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/Message.java b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/Message.java
new file mode 100644
index 0000000..46a93a8
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/Message.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.aggregator;
+
+import javax.xml.bind.annotation.XmlElement;
+
+import org.eclipse.persistence.oxm.annotations.XmlPath;
+
+/**
+ * Message bean.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class Message {
+
+ @XmlElement
+ private String text;
+
+ @XmlPath("user/profile_image_url/text()")
+ private String profileImg;
+
+ @XmlElement(nillable = true)
+ private String rgbColor;
+
+ public Message() {
+ }
+
+ public Message(final String text, final String rgbColor, final String profileImg) {
+ this.text = text;
+ this.rgbColor = rgbColor;
+ this.profileImg = profileImg;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public String getProfileImg() {
+ return profileImg;
+ }
+
+ public String getRgbColor() {
+ return rgbColor;
+ }
+
+ public void setRgbColor(String rgbColor) {
+ this.rgbColor = rgbColor;
+ }
+
+ @Override
+ public String toString() {
+ return "Message{"
+ + "text='" + text + '\''
+ + ", profileImg='" + profileImg + '\''
+ + ", rgpColor='" + rgbColor + '\''
+ + '}';
+ }
+}
diff --git a/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/MessageStreamResourceJaxRs.java b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/MessageStreamResourceJaxRs.java
new file mode 100644
index 0000000..94a2466
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/MessageStreamResourceJaxRs.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.aggregator;
+
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Logger;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+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.OutboundSseEvent;
+import javax.ws.rs.sse.Sse;
+import javax.ws.rs.sse.SseBroadcaster;
+import javax.ws.rs.sse.SseEventSink;
+
+import javax.inject.Singleton;
+
+/**
+ * Resource that aggregates incoming messages and broadcasts them
+ * to the registered Server-Sent Even (SSE) client streams.
+ * <p>
+ * Uses the JAX-RS 2.1 SSE API.
+ *
+ * @see MessageStreamResourceJersey
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("message/stream/jaxrs")
+@Singleton
+public final class MessageStreamResourceJaxRs {
+ private static final Logger LOGGER = Logger.getLogger(MessageStreamResourceJaxRs.class.getName());
+ private static AtomicLong nextMessageId = new AtomicLong(0);
+
+ private final Sse sse;
+ private final SseBroadcaster broadcaster;
+
+ public MessageStreamResourceJaxRs(@Context Sse sse) {
+ this.sse = sse;
+ this.broadcaster = sse.newBroadcaster();
+ }
+
+ /**
+ * Put a new message to the stream.
+ *
+ * The message will be broadcast to all registered SSE clients.
+ *
+ * @param message message to be broadcast.
+ */
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void putMessage(final Message message) {
+ LOGGER.info("--> Message received.");
+
+ final OutboundSseEvent event = sse.newEventBuilder()
+ .id(String.valueOf(nextMessageId.getAndIncrement()))
+ .mediaType(MediaType.APPLICATION_JSON_TYPE)
+ .data(Message.class, message)
+ .build();
+
+ broadcaster.broadcast(event);
+ }
+
+ /**
+ * Get the new SSE message stream channel.
+ */
+ @GET
+ @Produces(MediaType.SERVER_SENT_EVENTS)
+ public void getMessageStream(@Context SseEventSink eventSink) {
+ LOGGER.info("--> SSE connection received.");
+ broadcaster.register(eventSink);
+ }
+
+}
diff --git a/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/MessageStreamResourceJersey.java b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/MessageStreamResourceJersey.java
new file mode 100644
index 0000000..391776a
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/MessageStreamResourceJersey.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.aggregator;
+
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.media.sse.EventOutput;
+import org.glassfish.jersey.media.sse.OutboundEvent;
+import org.glassfish.jersey.media.sse.SseBroadcaster;
+import org.glassfish.jersey.media.sse.SseFeature;
+import org.glassfish.jersey.server.ChunkedOutput;
+
+/**
+ * Resource that aggregates incoming messages and broadcasts them
+ * to the registered Server-Sent Even (SSE) client streams.
+ * <p>
+ * Uses the Jersey-specific SSE API.
+ *
+ * @see MessageStreamResourceJaxRs
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Path("message/stream/jersey")
+public final class MessageStreamResourceJersey {
+ private static final Logger LOGGER = Logger.getLogger(MessageStreamResourceJersey.class.getName());
+
+ private static SseBroadcaster broadcaster = new SseBroadcaster() {
+ @Override
+ public void onException(final ChunkedOutput<OutboundEvent> chunkedOutput, final Exception exception) {
+ LOGGER.log(Level.SEVERE, "Error broadcasting message.", exception);
+ }
+ };
+ private static AtomicLong nextMessageId = new AtomicLong(0);
+
+ /**
+ * Put a new message to the stream.
+ *
+ * The message will be broadcast to all registered SSE clients.
+ *
+ * @param message message to be broadcast.
+ */
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void putMessage(final Message message) {
+ LOGGER.info("--> Message received.");
+
+ final OutboundEvent event = new OutboundEvent.Builder()
+ .id(String.valueOf(nextMessageId.getAndIncrement()))
+ .mediaType(MediaType.APPLICATION_JSON_TYPE)
+ .data(Message.class, message)
+ .build();
+
+ broadcaster.broadcast(event);
+ }
+
+ /**
+ * Get the new SSE message stream channel.
+ *
+ * @return new SSE message stream channel.
+ */
+ @GET
+ @Produces(SseFeature.SERVER_SENT_EVENTS)
+ public EventOutput getMessageStream() {
+ LOGGER.info("--> SSE connection received.");
+ final EventOutput eventOutput = new EventOutput();
+ broadcaster.add(eventOutput);
+ return eventOutput;
+ }
+
+}
diff --git a/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/RemoveSelectedListItemsAction.java b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/RemoveSelectedListItemsAction.java
new file mode 100644
index 0000000..34eb7d7
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/RemoveSelectedListItemsAction.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.aggregator;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.DefaultListModel;
+import javax.swing.JList;
+import javax.swing.KeyStroke;
+
+/**
+ * TODO: javadoc.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class RemoveSelectedListItemsAction extends AbstractAction {
+
+ private final JList list;
+ private final DefaultListModel listModel;
+
+ public RemoveSelectedListItemsAction(JList list, DefaultListModel model) {
+ if (list == null || model == null) {
+ throw new NullPointerException("Bound JList component and it's model must not be null.");
+ }
+
+ this.list = list;
+ this.listModel = model;
+
+ putValue(NAME, "Delete");
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ removeSelectedListItems();
+ }
+
+ private void removeSelectedListItems() {
+ Object[] selectedValues = list.getSelectedValues();
+ boolean itemsSelected = selectedValues.length > 0;
+
+ if (itemsSelected && confirmRemove()) {
+ for (Object selectedValue : selectedValues) {
+ listModel.removeElement(selectedValue);
+ }
+ }
+ }
+
+ private boolean confirmRemove() {
+ // E.g. JOptionPane-Confirm-Dialog
+ return true;
+ }
+}
diff --git a/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/TestAggregatorJaxRs.java b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/TestAggregatorJaxRs.java
new file mode 100644
index 0000000..bc151f1
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/TestAggregatorJaxRs.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.aggregator;
+
+/**
+ * Fake message aggregator used for testing purposes pointing to SSE event stream implemented using Jersey-specific API.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public class TestAggregatorJaxRs extends AbstractTestAggregator {
+
+ public TestAggregatorJaxRs(String rgbColor) {
+ super(rgbColor);
+ }
+
+ @Override
+ public String getPath() {
+ return "message/stream/jaxrs";
+ }
+
+ @Override
+ protected String getPrefix() {
+ return "JAX-RS aggregator: ";
+ }
+}
diff --git a/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/TestAggregatorJersey.java b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/TestAggregatorJersey.java
new file mode 100644
index 0000000..c5ca5dc
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/TestAggregatorJersey.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.aggregator;
+
+/**
+ * Fake message aggregator used for testing purposes pointing to SSE event stream implemented using Jersey-specific API.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public class TestAggregatorJersey extends AbstractTestAggregator {
+
+ public TestAggregatorJersey(String rgbColor) {
+ super(rgbColor);
+ }
+
+ @Override
+ public String getPath() {
+ return "message/stream/jersey";
+ }
+
+ @Override
+ protected String getPrefix() {
+ return "Jersey aggregator: ";
+ }
+
+}
diff --git a/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/TwitterAggregator.java b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/TwitterAggregator.java
new file mode 100644
index 0000000..a4a0aa2
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/java/org/glassfish/jersey/examples/aggregator/TwitterAggregator.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.aggregator;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.SslConfigurator;
+import org.glassfish.jersey.client.ChunkedInput;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
+import org.glassfish.jersey.message.GZipEncoder;
+import org.glassfish.jersey.moxy.json.MoxyJsonFeature;
+
+/**
+ * Twitter message-based data aggregator implementation.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class TwitterAggregator implements DataAggregator {
+ private static final Logger LOGGER = Logger.getLogger(TwitterAggregator.class.getName());
+
+ private volatile boolean cancelled;
+ private final String rgbColor;
+
+ /**
+ * Create new twitter message aggregator with a specific message color.
+ *
+ * @param rgbColor message color.
+ */
+ public TwitterAggregator(String rgbColor) {
+ this.rgbColor = rgbColor;
+ }
+
+ @Override
+ public void start(final String keywords, final DataListener msgListener) {
+ cancelled = false;
+
+// System.setProperty("http.proxyHost", "www-proxy.us.oracle.com");
+// System.setProperty("http.proxyPort", "80");
+// System.setProperty("https.proxyHost", "www-proxy.us.oracle.com");
+// System.setProperty("https.proxyPort", "80");
+
+
+ final LinkedBlockingQueue<Message> messages = new LinkedBlockingQueue<Message>();
+
+ final Future<?> readerHandle = Executors.newSingleThreadExecutor().submit(new Runnable() {
+ @Override
+ public void run() {
+ SslConfigurator sslConfig = SslConfigurator.newInstance()
+ .trustStoreFile("./truststore_client")
+ .trustStorePassword("asdfgh")
+
+ .keyStoreFile("./keystore_client")
+ .keyPassword("asdfgh");
+
+ final Client client = ClientBuilder.newBuilder().sslContext(sslConfig.createSSLContext()).build();
+ client.property(ClientProperties.CONNECT_TIMEOUT, 2000)
+ .register(new MoxyJsonFeature())
+ .register(HttpAuthenticationFeature.basic(App.getTwitterUserName(), App.getTwitterUserPassword()))
+ .register(GZipEncoder.class);
+
+ final Response response = client.target("https://stream.twitter.com/1.1/statuses/filter.json")
+ .queryParam("track", keywords)
+// .queryParam("locations", "-122.75,36.8,-121.75,37.8") // San Francisco
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .header(HttpHeaders.HOST, "stream.twitter.com")
+ .header(HttpHeaders.USER_AGENT, "Jersey/2.0")
+ .header(HttpHeaders.ACCEPT_ENCODING, "gzip")
+ .get();
+
+ if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) {
+ LOGGER.log(Level.WARNING, "Error connecting to Twitter Streaming API: " + response.getStatus());
+ msgListener.onError();
+ return;
+ }
+ msgListener.onStart();
+
+ try {
+ final ChunkedInput<Message> chunks = response.readEntity(new GenericType<ChunkedInput<Message>>() {
+ });
+ try {
+ while (!Thread.interrupted()) {
+ Message message = chunks.read();
+ if (message == null) {
+ break;
+ }
+ try {
+ message.setRgbColor(rgbColor);
+ System.out.println(message.toString());
+ messages.put(message);
+ } catch (InterruptedException e) {
+ break;
+ }
+ }
+ } finally {
+ if (chunks != null) {
+ chunks.close();
+ }
+ }
+ } catch (Throwable t) {
+ LOGGER.log(Level.WARNING, "Reading from the Twitter stream has failed", t);
+ messages.offer(null);
+ msgListener.onError();
+ }
+ }
+ });
+
+ Executors.newSingleThreadExecutor().submit(new Runnable() {
+ @Override
+ public void run() {
+ final Client resourceClient = ClientBuilder.newClient();
+ resourceClient.register(new MoxyJsonFeature());
+ final WebTarget messageStreamResource = resourceClient.target(App.getApiUri()).path("message/stream");
+
+ Message message = null;
+ try {
+ while (!cancelled && (message = messages.take()) != null) {
+ msgListener.onMessage(message);
+
+ final Response r = messageStreamResource.request().put(Entity.json(message));
+ if (r.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) {
+ LOGGER.warning("Unexpected PUT message response status code: " + r.getStatus());
+ }
+ }
+
+ if (message == null) {
+ LOGGER.info("Timed out while waiting for a message.");
+ }
+ } catch (InterruptedException ex) {
+ LOGGER.log(Level.WARNING, "Waiting for a message has been interrupted.", ex);
+ } finally {
+ readerHandle.cancel(true);
+ msgListener.onComplete();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void stop() {
+ cancelled = true;
+ }
+}
diff --git a/examples/sse-twitter-aggregator/src/main/resources/webroot/css/main.css b/examples/sse-twitter-aggregator/src/main/resources/webroot/css/main.css
new file mode 100644
index 0000000..4813510
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/resources/webroot/css/main.css
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+div.message {
+ border: thick solid;
+ border-radius: 10px;
+ margin: 10px;
+ width: 300px;
+ padding: 10px;
+ box-shadow: 5px 5px 5px #888888;
+ font-family: Helvetica, serif;
+ color: #444444;
+}
diff --git a/examples/sse-twitter-aggregator/src/main/resources/webroot/index.html b/examples/sse-twitter-aggregator/src/main/resources/webroot/index.html
new file mode 100644
index 0000000..5ebfceb
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/resources/webroot/index.html
@@ -0,0 +1,22 @@
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <title>Jersey Message Streaming Example</title>
+ <link rel="stylesheet" href="css/main.css" />
+ <script src="js/sse-client.js" type="text/javascript"></script>
+</head>
+<body>
+</body>
+</html>
diff --git a/examples/sse-twitter-aggregator/src/main/resources/webroot/js/sse-client.js b/examples/sse-twitter-aggregator/src/main/resources/webroot/js/sse-client.js
new file mode 100644
index 0000000..b8af823
--- /dev/null
+++ b/examples/sse-twitter-aggregator/src/main/resources/webroot/js/sse-client.js
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+"use strict";
+
+function receiveMessages() {
+ if (typeof(EventSource) !== "undefined") {
+ // Yes! Server-sent events support!
+ var sourceJersey = new EventSource('/aggregator-api/message/stream/jersey');
+ var sourceJaxRs = new EventSource('/aggregator-api/message/stream/jaxrs');
+ var eventHandler =function (event) {
+ var data = JSON.parse(event.data);
+ console.log(data);
+
+ var newEntry =
+ '<div class="message">'
+ + '<img src="' + data.user.profile_image_url + '" />'
+ + '<span style="color: #' + data.rgbColor + '">'
+ + data.text + '</span></div>';
+
+ document.body.innerHTML = newEntry + document.body.innerHTML;
+ };
+ sourceJersey.onmessage = eventHandler;
+ sourceJaxRs.onmessage = eventHandler;
+
+ sourceJersey.onopen = function (event) {
+ // Connection was opened.
+ console.log('Jresey stream opened.')
+ };
+
+ sourceJaxRs.onopen = function (event) {
+ console.log('JAX-RS stream opened.')
+ }
+
+ sourceJersey.onclose = function (event) {
+ // Connection was closed.
+ console.log('Jersey connection closed')
+ };
+
+ sourceJaxRs.onclose = function (event) {
+ // Connection was closed.
+ console.log('JAX-RS connection closed')
+ };
+ } else {
+ // Sorry! No server-sent events support..
+ console.log('SSE not supported by browser.')
+ }
+}
+
+window.onload = receiveMessages ;
diff --git a/examples/sse-twitter-aggregator/truststore_client b/examples/sse-twitter-aggregator/truststore_client
new file mode 100644
index 0000000..74784fb
--- /dev/null
+++ b/examples/sse-twitter-aggregator/truststore_client
Binary files differ
diff --git a/examples/sse-twitter-aggregator/twitter-api.properties b/examples/sse-twitter-aggregator/twitter-api.properties
new file mode 100644
index 0000000..1d419bc
--- /dev/null
+++ b/examples/sse-twitter-aggregator/twitter-api.properties
@@ -0,0 +1,12 @@
+#
+# Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Distribution License v. 1.0, which is available at
+# http://www.eclipse.org/org/documents/edl-v10.php.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+twitter.user.name=<enter_your_twitter_account_name_here>
+twitter.user.password=<enter_your_twitter_account_password_here>
diff --git a/examples/system-properties-example/README.MD b/examples/system-properties-example/README.MD
new file mode 100644
index 0000000..a196bdf
--- /dev/null
+++ b/examples/system-properties-example/README.MD
@@ -0,0 +1,36 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Helloworld webapp Example
+=========================
+
+This example demonstrates how to manage system properties using REST API. System Properties Example
+is able to expose all registered system properties, find out its values and even change a given
+property value.
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+------------------------------- | ----------------------- | --------------
+**_/properties_** | PropertyNamesResource | GET
+**_/properties/{prop_name}_** | PropertyResource | GET
+**_/properties/{prop_name}_** | PropertyResource | PUT
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container. You can access the application at:
+
+- <http://localhost:8080/properties>
\ No newline at end of file
diff --git a/examples/system-properties-example/pom.xml b/examples/system-properties-example/pom.xml
new file mode 100644
index 0000000..c3657a2
--- /dev/null
+++ b/examples/system-properties-example/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>system-properties-example</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-system-properties</name>
+
+ <description>Jersey system properties example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-proxy-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.sysprops.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/App.java b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/App.java
new file mode 100644
index 0000000..65ae843
--- /dev/null
+++ b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/App.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sysprops;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.examples.sysprops.impl.PropertyNamesResourceImpl;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * @author Martin Matula
+ */
+public class App {
+ private static final URI BASE_URI = URI.create("http://localhost:8080/");
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("System Properties Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, createApp(), false);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(
+ String.format("Application started.%n"
+ + "Try out %s%n"
+ + "Stop the application using CTRL+C",
+ BASE_URI + "/properties"));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ public static ResourceConfig createApp() {
+ return new ResourceConfig(
+ PropertyNamesResourceImpl.class,
+ PropertiesWriter.class
+ );
+ }
+}
diff --git a/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/PropertiesReader.java b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/PropertiesReader.java
new file mode 100644
index 0000000..35dd392
--- /dev/null
+++ b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/PropertiesReader.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sysprops;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+
+import org.glassfish.jersey.message.MessageUtils;
+
+/**
+ * @author Martin Matula
+ */
+@Consumes(MediaType.TEXT_PLAIN)
+public class PropertiesReader implements MessageBodyReader<Set<String>> {
+
+ @Override
+ public boolean isReadable(final Class<?> type,
+ final Type genericType,
+ final Annotation[] annotations,
+ final MediaType mediaType) {
+ return Set.class.isAssignableFrom(type);
+ }
+
+ @Override
+ public Set<String> readFrom(final Class<Set<String>> type,
+ final Type genericType,
+ final Annotation[] annotations,
+ final MediaType mediaType,
+ final MultivaluedMap<String, String> httpHeaders,
+ final InputStream entityStream) throws IOException, WebApplicationException {
+ final BufferedReader br = new BufferedReader(new InputStreamReader(entityStream, MessageUtils.getCharset(mediaType)));
+ final Set<String> result = new HashSet<>();
+ String line;
+ while ((line = br.readLine()) != null) {
+ result.add(line);
+ }
+ return result;
+ }
+}
diff --git a/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/PropertiesWriter.java b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/PropertiesWriter.java
new file mode 100644
index 0000000..99b8472
--- /dev/null
+++ b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/PropertiesWriter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sysprops;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.Charset;
+import java.util.Set;
+
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import org.glassfish.jersey.message.MessageUtils;
+
+/**
+ * @author Martin Matula
+ */
+@Produces(MediaType.TEXT_PLAIN)
+public class PropertiesWriter implements MessageBodyWriter<Set<String>> {
+ @Override
+ public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return Set.class.isAssignableFrom(type);
+ }
+
+ @Override
+ public long getSize(final Set<String> s, final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(final Set<String> s, final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
+ final OutputStream entityStream) throws IOException, WebApplicationException {
+ final PrintStream ps = new PrintStream(entityStream, true, MessageUtils.getCharset(mediaType).name());
+
+ for (final String item : s) {
+ ps.println(item);
+ }
+ }
+}
diff --git a/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/PropertyNamesResource.java b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/PropertyNamesResource.java
new file mode 100644
index 0000000..037e0a9
--- /dev/null
+++ b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/PropertyNamesResource.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sysprops;
+
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author Martin Matula
+ */
+@Path("properties")
+@Produces(MediaType.TEXT_PLAIN)
+@Consumes(MediaType.TEXT_PLAIN)
+public interface PropertyNamesResource {
+ @GET
+ Set<String> getPropertyNames();
+
+ @Path("{name}")
+ public PropertyResource getProperty(@PathParam("name") String name);
+}
diff --git a/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/PropertyResource.java b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/PropertyResource.java
new file mode 100644
index 0000000..33e8954
--- /dev/null
+++ b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/PropertyResource.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sysprops;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author Martin Matula
+ */
+@Produces(MediaType.TEXT_PLAIN)
+@Consumes(MediaType.TEXT_PLAIN)
+public interface PropertyResource {
+ @GET
+ String get();
+
+ @PUT
+ String set(String value);
+}
diff --git a/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/impl/PropertyNamesResourceImpl.java b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/impl/PropertyNamesResourceImpl.java
new file mode 100644
index 0000000..d5d3eb8
--- /dev/null
+++ b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/impl/PropertyNamesResourceImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sysprops.impl;
+
+import java.util.Set;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+import org.glassfish.jersey.examples.sysprops.PropertyNamesResource;
+import org.glassfish.jersey.examples.sysprops.PropertyResource;
+
+/**
+ * @author Martin Matula
+ */
+public class PropertyNamesResourceImpl implements PropertyNamesResource {
+ @Context
+ private UriInfo uriInfo;
+
+ @Override
+ public Set<String> getPropertyNames() {
+ return System.getProperties().stringPropertyNames();
+ }
+
+ @Override
+ public PropertyResource getProperty(String name) {
+ return new PropertyResourceImpl(name, uriInfo);
+ }
+}
diff --git a/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/impl/PropertyResourceImpl.java b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/impl/PropertyResourceImpl.java
new file mode 100644
index 0000000..d479417
--- /dev/null
+++ b/examples/system-properties-example/src/main/java/org/glassfish/jersey/examples/sysprops/impl/PropertyResourceImpl.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sysprops.impl;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.glassfish.jersey.examples.sysprops.PropertyResource;
+
+/**
+ * @author Martin Matula
+ */
+public class PropertyResourceImpl implements PropertyResource {
+ private final String name;
+ private final UriInfo uriInfo;
+
+ public PropertyResourceImpl(String name, UriInfo uriInfo) {
+ this.name = name;
+ this.uriInfo = uriInfo;
+ }
+
+ @Override
+ public String get() {
+ String value = System.getProperty(name);
+ if (value == null) {
+ throw new WebApplicationException(404);
+ }
+ return value;
+ }
+
+ @Override
+ public String set(String value) {
+ if (System.setProperty(name, value) == null) {
+ throw new WebApplicationException(Response.created(uriInfo.getRequestUri()).entity(value).build());
+ }
+ return value;
+ }
+}
diff --git a/examples/system-properties-example/src/test/java/org/glassfish/jersey/examples/sysprops/SysPropsTest.java b/examples/system-properties-example/src/test/java/org/glassfish/jersey/examples/sysprops/SysPropsTest.java
new file mode 100644
index 0000000..21887fc
--- /dev/null
+++ b/examples/system-properties-example/src/test/java/org/glassfish/jersey/examples/sysprops/SysPropsTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.sysprops;
+
+import java.util.Set;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.proxy.WebResourceFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * System properties example unit test.
+ *
+ * @author Martin Matula
+ */
+public class SysPropsTest extends JerseyTest {
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ return App.createApp();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(PropertiesReader.class);
+ }
+
+ @Test
+ public void testGetPropertyNames() {
+ PropertyNamesResource propertyNamesResource = WebResourceFactory.newResource(PropertyNamesResource.class, target());
+ Set<String> propertyNames = propertyNamesResource.getPropertyNames();
+ assertEquals(System.getProperties().stringPropertyNames(), propertyNames);
+ }
+
+ @Test
+ public void testGetProperty() {
+ PropertyNamesResource pnr = WebResourceFactory.newResource(PropertyNamesResource.class, target());
+
+ assertEquals(System.getProperty("java.home"), pnr.getProperty("java.home").get());
+ }
+
+ @Test
+ public void testSetProperty() {
+ PropertyNamesResource pnr = WebResourceFactory.newResource(PropertyNamesResource.class, target());
+
+ pnr.getProperty("test").set("this is a test");
+ assertEquals(System.getProperty("test"), pnr.getProperty("test").get());
+ }
+}
diff --git a/examples/webapp-example-parent/pom.xml b/examples/webapp-example-parent/pom.xml
new file mode 100644
index 0000000..aba706e
--- /dev/null
+++ b/examples/webapp-example-parent/pom.xml
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>webapp-example-parent</artifactId>
+ <packaging>pom</packaging>
+ <name>jersey-examples-webapp-parent-pom</name>
+
+ <description>Jersey Web Application (Servlet) examples parent POM.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>assemble-gf-src-zip</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>transform</goal>
+ </goals>
+ <configuration>
+ <transformationSets>
+ <transformationSet>
+ <dir>${project.basedir}</dir>
+ <includes>
+ <include>pom.xml</include>
+ </includes>
+ <stylesheet>../etc/gf-project-src-pom.xsl</stylesheet>
+ <outputDir>${project.build.directory}/gf-pom-file</outputDir>
+ </transformationSet>
+ </transformationSets>
+ </configuration>
+ </execution>
+ <execution>
+ <id>assemble-wls-src-zip</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>transform</goal>
+ </goals>
+ <configuration>
+ <transformationSets>
+ <transformationSet>
+ <dir>${project.basedir}</dir>
+ <includes>
+ <include>pom.xml</include>
+ </includes>
+ <stylesheet>../etc/wls-project-src-pom.xsl</stylesheet>
+ <outputDir>${project.build.directory}/wls-pom-file</outputDir>
+ </transformationSet>
+ <transformationSet>
+ <dir>${project.basedir}</dir>
+ <includes>
+ <include>**/web.xml</include>
+ </includes>
+ <stylesheet>../etc/wls-project-src-web.xsl</stylesheet>
+ <outputDir>${project.build.directory}/wls-web-xml-file</outputDir>
+ </transformationSet>
+ </transformationSets>
+ </configuration>
+ </execution>
+ <execution>
+ <id>assemble-wls1213-src-zip</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>transform</goal>
+ </goals>
+ <configuration>
+ <transformationSets>
+ <transformationSet>
+ <dir>${project.basedir}</dir>
+ <includes>
+ <include>pom.xml</include>
+ </includes>
+ <stylesheet>../etc/wls1213-project-src-pom.xsl</stylesheet>
+ <outputDir>${project.build.directory}/wls1213-pom-file</outputDir>
+ </transformationSet>
+ <transformationSet>
+ <dir>${project.basedir}</dir>
+ <includes>
+ <include>**/web.xml</include>
+ </includes>
+ <stylesheet>../etc/wls-project-src-web.xsl</stylesheet>
+ <outputDir>${project.build.directory}/wls1213-web-xml-file</outputDir>
+ </transformationSet>
+ </transformationSets>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>assemble-gf-src-zip</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <!-- Reference to a descriptor in org.glassfish.jersey.examples:assemblies module -->
+ <descriptorRef>glassfish-src-zip</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ <execution>
+ <id>assemble-wls-src-zip</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <!-- Reference to a descriptor in org.glassfish.jersey.examples:assemblies module -->
+ <descriptorRef>weblogic-src-zip</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ <execution>
+ <id>assemble-wls1213-src-zip</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <!-- Reference to a descriptor in org.glassfish.jersey.examples:assemblies module -->
+ <descriptorRef>weblogic1213-src-zip</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>maven-jetty-plugin</artifactId>
+ <configuration>
+ <webApp>${project.build.directory}/${project.artifactId}.war</webApp>
+ <contextPath>${project.artifactId}</contextPath>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+</project>
diff --git a/examples/xml-moxy/README.MD b/examples/xml-moxy/README.MD
new file mode 100644
index 0000000..8c67e04
--- /dev/null
+++ b/examples/xml-moxy/README.MD
@@ -0,0 +1,117 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+MOXy Example
+============
+
+What is MOXy
+------------
+
+This example demonstrates that you can utilize MOXy extensions when
+dealing with XML representation when developing a Jersey based RESTful
+Web application
+
+MOXy is EclipseLink's Object to XML Mapping services. MOXy allows for a
+POJO object model to be mapped to an XML schema. The Java Architecture
+for XML Binding (JAXB) provides a Java standard for object XML mapping
+(OXM). MOXy supports JAXB, as well as providing its' own native API and
+integration with Web Services.
+
+MOXy XML Path Mapping Extension
+-------------------------------
+
+The MOXy extension shown in this example is described on the Eclipse
+Wiki site at
+<http://wiki.eclipse.org/EclipseLink/Examples/MOXy/GettingStarted/MOXyExtensions>.
+It allows you to specify path based mapping via @XmlPath annotation.
+
+If the MOXy extension was not used, the XML represenation of the
+customer data would look like follows:
+
+```xml
+<customer>
+ <name>Jane Doe</name>
+ <address>
+ <city>My Town</city>
+ <street>123 Any Street</street>
+ </address>
+ <phoneNumbers type="work">613-555-1111</phoneNumbers>
+ <phoneNumbers type="cell">613-555-2222</phoneNumbers>
+</customer>
+```
+
+By adding `@org.eclipse.persistence.oxm.annotations.XmlPath` annotation
+to the bean definition classes, you will get the following XML
+representation instead:
+
+```xml
+<customer>
+ <personal-info>
+ <name>Jane Doe</name>
+ </personal-info>
+ <contact-info>
+ <address>
+ <city>My Town</city>
+ <street>123 Any Street</street>
+ </address>
+ <phone-number type="work">613-555-1111</phone-number>
+ <phone-number type="cell">613-555-2222</phone-number>
+ </contact-info>
+</customer>
+```
+
+XML Path expressions used are:
+
+- personal-info/name/text()
+- contact-info/address
+- contact-info/phone-number
+
+Please check out the source code and the wiki page linked above for the
+detailed information on the XML Path mapping feature.
+
+Replacing Implicit JAXB Runtime With MOXy
+-----------------------------------------
+
+Since MOXy is a JAXB implementation, the example still utilizes the
+standard Jersey JAXB message body reader/writer providers. To make
+Jersey use MOXy runtime, you just need to put a `jaxb.properties` file
+into the Java package containing your JAXB beans. The file should have
+the following content:
+
+ javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
+
+Concrete file used in this example is placed under
+
+ src/main/resources/com/sun/jersey/samples/moxy/beans/jaxb.properties
+
+Contents
+--------
+
+The example consists of a single REST Resource represented by the
+following Java class:
+
+ org.glassfish.jersey.examples.xmlmoxy.CustomerResource
+
+A resource class that maintains a single customer data.
+
+The mapping of the URI path space is presented in the following table:
+
+URI path | Resource class | HTTP methods
+---------------- | ------------------ | --------------
+**_/customer_** | CustomerResource | GET, PUT
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+> mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container. You can access the application at:
+
+- [http://localhost:8080/moxy/customer](http://localhost:8080/xml-moxy/customer)
diff --git a/examples/xml-moxy/pom.xml b/examples/xml-moxy/pom.xml
new file mode 100644
index 0000000..f6ffdef
--- /dev/null
+++ b/examples/xml-moxy/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Distribution License v. 1.0, which is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+ SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.glassfish.jersey.examples</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>xml-moxy</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-examples-moxy</name>
+
+ <description>Jersey XML MOXy example.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-grizzly2-http</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-moxy</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.glassfish.jersey.examples.xmlmoxy.App</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/App.java b/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/App.java
new file mode 100644
index 0000000..d6cd52c
--- /dev/null
+++ b/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/App.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.xmlmoxy;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.moxy.xml.MoxyXmlFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class App {
+
+ private static final URI BASE_URI = URI.create("http://localhost:8080/xml-moxy/");
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("XML with MOXy Jersey Example App");
+
+ final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, createApp());
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ server.shutdownNow();
+ }
+ }));
+ server.start();
+
+ System.out.println(String.format("Application started.%nTry out %s%nStop the application using CTRL+C",
+ BASE_URI + "/customer"));
+
+ Thread.currentThread().join();
+ } catch (IOException | InterruptedException ex) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ public static ResourceConfig createApp() {
+ return new ResourceConfig().packages("org.glassfish.jersey.examples.xmlmoxy").register(new MoxyXmlFeature());
+ }
+}
diff --git a/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/CustomerResource.java b/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/CustomerResource.java
new file mode 100644
index 0000000..7fb5ee0
--- /dev/null
+++ b/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/CustomerResource.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.xmlmoxy;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.examples.xmlmoxy.beans.Address;
+import org.glassfish.jersey.examples.xmlmoxy.beans.Customer;
+import org.glassfish.jersey.examples.xmlmoxy.beans.PhoneNumber;
+
+@Path("/customer")
+public class CustomerResource {
+
+ private static Customer customer = createInitialCustomer();
+
+ @GET
+ @Produces(MediaType.APPLICATION_XML)
+ public Customer getCustomer() {
+ return customer;
+ }
+
+ @PUT
+ @Consumes(MediaType.APPLICATION_XML)
+ public void setCustomer(final Customer c) {
+ setCustomerToStatic(c);
+ }
+
+ private static Customer createInitialCustomer() {
+ final Customer result = new Customer();
+
+ result.setName("Jane Doe");
+ result.setAddress(new Address("123 Any Street", "My Town"));
+ result.getPhoneNumbers().add(new PhoneNumber("work", "613-555-1111"));
+ result.getPhoneNumbers().add(new PhoneNumber("cell", "613-555-2222"));
+
+ return result;
+ }
+
+ private static void setCustomerToStatic(final Customer customer) {
+ CustomerResource.customer = customer;
+ }
+}
diff --git a/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/beans/Address.java b/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/beans/Address.java
new file mode 100644
index 0000000..26238f1
--- /dev/null
+++ b/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/beans/Address.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.xmlmoxy.beans;
+
+/**
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class Address {
+
+ private String street;
+ private String city;
+
+ public Address(){}
+
+ public Address(String street, String city) {
+ this.street = street;
+ this.city = city;
+ }
+
+ public String getStreet() {
+ return street;
+ }
+
+ public void setStreet(String street) {
+ this.street = street;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Address other = (Address) obj;
+ if ((this.street == null) ? (other.street != null) : !this.street.equals(other.street)) {
+ return false;
+ }
+ if ((this.city == null) ? (other.city != null) : !this.city.equals(other.city)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 29 * hash + (this.street != null ? this.street.hashCode() : 0);
+ hash = 29 * hash + (this.city != null ? this.city.hashCode() : 0);
+ return hash;
+ }
+}
diff --git a/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/beans/Customer.java b/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/beans/Customer.java
new file mode 100644
index 0000000..f1f6b33
--- /dev/null
+++ b/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/beans/Customer.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.xmlmoxy.beans;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+import org.eclipse.persistence.oxm.annotations.XmlPath;
+
+/**
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@XmlRootElement
+@XmlType(propOrder = {"name", "address", "phoneNumbers"})
+public class Customer {
+
+ private String name;
+ private Address address;
+ private List<PhoneNumber> phoneNumbers;
+
+ public Customer() {
+ phoneNumbers = new ArrayList<PhoneNumber>();
+ }
+
+ @XmlPath("personal-info/name/text()")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @XmlPath("contact-info/address")
+ public Address getAddress() {
+ return address;
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+
+ @XmlPath("contact-info/phone-number")
+ public List<PhoneNumber> getPhoneNumbers() {
+ return phoneNumbers;
+ }
+
+ public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
+ this.phoneNumbers = phoneNumbers;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Customer other = (Customer) obj;
+ if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
+ return false;
+ }
+ if (this.address != other.address && (this.address == null || !this.address.equals(other.address))) {
+ return false;
+ }
+ if (this.phoneNumbers != other.phoneNumbers && (this.phoneNumbers == null || !this.phoneNumbers
+ .equals(other.phoneNumbers))) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0);
+ hash = 97 * hash + (this.address != null ? this.address.hashCode() : 0);
+ hash = 97 * hash + (this.phoneNumbers != null ? this.phoneNumbers.hashCode() : 0);
+ return hash;
+ }
+}
diff --git a/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/beans/PhoneNumber.java b/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/beans/PhoneNumber.java
new file mode 100644
index 0000000..5df8b0e
--- /dev/null
+++ b/examples/xml-moxy/src/main/java/org/glassfish/jersey/examples/xmlmoxy/beans/PhoneNumber.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.xmlmoxy.beans;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlValue;
+
+/**
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class PhoneNumber {
+
+ private String type;
+ private String value;
+
+ public PhoneNumber() {}
+
+ public PhoneNumber(String type, String value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ @XmlAttribute
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ @XmlValue
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final PhoneNumber other = (PhoneNumber) obj;
+ if ((this.type == null) ? (other.type != null) : !this.type.equals(other.type)) {
+ return false;
+ }
+ if ((this.value == null) ? (other.value != null) : !this.value.equals(other.value)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 59 * hash + (this.type != null ? this.type.hashCode() : 0);
+ hash = 59 * hash + (this.value != null ? this.value.hashCode() : 0);
+ return hash;
+ }
+}
diff --git a/examples/xml-moxy/src/test/java/org/glassfish/jersey/examples/xmlmoxy/MoxyAppTest.java b/examples/xml-moxy/src/test/java/org/glassfish/jersey/examples/xmlmoxy/MoxyAppTest.java
new file mode 100644
index 0000000..4d207a0
--- /dev/null
+++ b/examples/xml-moxy/src/test/java/org/glassfish/jersey/examples/xmlmoxy/MoxyAppTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.xmlmoxy;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.examples.xmlmoxy.beans.Customer;
+import org.glassfish.jersey.moxy.xml.MoxyXmlFeature;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class MoxyAppTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ enable(TestProperties.DUMP_ENTITY);
+
+ return App.createApp();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(new MoxyXmlFeature());
+ }
+
+ /**
+ * Test that the expected response is sent back.
+ * @throws java.lang.Exception
+ */
+ @Test
+ public void testCustomer() throws Exception {
+ final WebTarget webTarget = target().path("customer");
+
+ Customer customer = webTarget.request(MediaType.APPLICATION_XML).get(Customer.class);
+ customer.setName("Tom Dooley");
+ webTarget.request(MediaType.APPLICATION_XML).put(Entity.entity(customer, MediaType.APPLICATION_XML));
+
+ Customer updatedCustomer = webTarget.request(MediaType.APPLICATION_XML).get(Customer.class);
+ assertEquals(customer, updatedCustomer);
+ }
+}