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