Initial Contribution
Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/incubator/declarative-linking/pom.xml b/incubator/declarative-linking/pom.xml
new file mode 100644
index 0000000..592cb2a
--- /dev/null
+++ b/incubator/declarative-linking/pom.xml
@@ -0,0 +1,167 @@
+<?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 Public License v. 2.0, which is available at
+ http://www.eclipse.org/legal/epl-2.0.
+
+ This Source Code may also be made available under the following Secondary
+ Licenses when the conditions for such availability set forth in the
+ Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ version 2 with the GNU Classpath Exception, which is available at
+ https://www.gnu.org/software/classpath/license.html.
+
+ SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<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.incubator</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-declarative-linking</artifactId>
+ <packaging>jar</packaging>
+
+ <name>jersey-declarative-linking</name>
+
+ <description>
+ Jersey support for declarative hyperlinking.
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.el</groupId>
+ <artifactId>javax.el-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.web</groupId>
+ <artifactId>javax.el</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-jackson</artifactId>
+ <version>${jersey.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.skyscreamer</groupId>
+ <artifactId>jsonassert</artifactId>
+ <version>1.4.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <links>
+ <link>https://jsr311.java.net/nonav/releases/1.1</link>
+ <link>https://jersey.java.net/nonav/apidocs/latest/jersey/</link>
+ </links>
+ <notimestamp>true</notimestamp>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>cobertura</id>
+ <activation>
+ <property>
+ <name>cobertura</name>
+ </property>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ <version>${project.version}</version>
+ <classifier>cobertura</classifier>
+ </dependency>
+ </dependencies>
+ </profile>
+ <profile>
+ <id>default</id>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+ </profile>
+ </profiles>
+
+ <build>
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/java</directory>
+ <includes>
+ <include>META-INF/**/*</include>
+ </includes>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <phase>package</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/Binding.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/Binding.java
new file mode 100644
index 0000000..94b5bf2
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/Binding.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.Beta;
+
+/**
+ * Specifies the binding between a URI template parameter and a bean property.
+ * @see org.glassfish.jersey.linking.InjectLink#bindings()
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+@Target({})
+@Retention(RetentionPolicy.RUNTIME)
+@Beta
+public @interface Binding {
+
+ /**
+ * Specifies the name of the URI template parameter, defaults to
+ * "value" for convenience.
+ */
+ String name() default "value";
+
+ /**
+ * Specifies the value of a URI template parameter. The value is an EL
+ * expression using immediate evaluation syntax. E.g.:
+ * <pre>${instance.widgetId}</pre>
+ * In the above example the value is taken from the <code>widgetId</code>
+ * property of the implicit <code>instance</code> bean.
+ * <p>Three implicit beans are supported:</p>
+ * <dl>
+ * <dt><code>instance</code></dt><dd>The object whose class contains the
+ * {@link org.glassfish.jersey.linking.InjectLink} annotation.</dd>
+ * <dt><code>entity</code></dt><dd>The entity returned by the resource
+ * class method. This is either the resource method return value
+ * or the entity property for a resource method that returns Response.</dd>
+ * <dt><code>resource</code></dt><dd>The resource class instance that
+ * returned the object that contains the {@code InjectLink} annotation.</dd>
+ * </dd>
+ * </dl>
+ */
+ String value();
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/DeclarativeLinkingFeature.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/DeclarativeLinkingFeature.java
new file mode 100644
index 0000000..a215464
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/DeclarativeLinkingFeature.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.Beta;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.linking.contributing.NaiveResourceLinkContributionContext;
+import org.glassfish.jersey.linking.contributing.ResourceLinkContributionContext;
+import org.glassfish.jersey.linking.mapping.NaiveResourceMappingContext;
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+/**
+ * A feature to enable the declarative linking functionality.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+@Beta
+public class DeclarativeLinkingFeature implements Feature {
+
+ @Override
+ public boolean configure(FeatureContext context) {
+
+ Configuration config = context.getConfiguration();
+ if (!config.isRegistered(ResponseLinkFilter.class)) {
+ context.register(new AbstractBinder() {
+
+ @Override
+ protected void configure() {
+ bindAsContract(NaiveResourceMappingContext.class)
+ .to(ResourceMappingContext.class).in(Singleton.class);
+ }
+ });
+ context.register(new AbstractBinder() {
+
+ @Override
+ protected void configure() {
+ bindAsContract(NaiveResourceLinkContributionContext.class)
+ .to(ResourceLinkContributionContext.class).in(Singleton.class);
+ }
+ });
+
+ context.register(ResponseLinkFilter.class);
+
+ // TODO: map values back?
+ // context.register(RequestLinkFilter.class);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ELLinkBuilder.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ELLinkBuilder.java
new file mode 100644
index 0000000..8e777be
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ELLinkBuilder.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import javax.el.ExpressionFactory;
+import javax.el.ValueExpression;
+
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+import org.glassfish.jersey.uri.internal.UriTemplateParser;
+
+/**
+ * A helper class to build links from EL expressions.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+final class ELLinkBuilder {
+
+ private ELLinkBuilder() {
+ }
+
+ private static final ExpressionFactory expressionFactory =
+ ExpressionFactory.newInstance();
+
+ /**
+ * Evaluates the condition
+ *
+ * @param condition the condition expression
+ * @param entity the entity returned from the resource method
+ * @param resource the resource class instance that returned the entity
+ * @param instance the instance that contains the entity, e.g. the value of a field within an entity class.
+ * @return the result of the condition
+ */
+ static boolean evaluateCondition(String condition,
+ Object entity,
+ Object resource,
+ Object instance) {
+
+ if (condition == null || condition.isEmpty()) {
+ return true;
+ }
+ LinkELContext context = new LinkELContext(entity, resource, instance);
+ ValueExpression expr = expressionFactory.createValueExpression(context, condition, boolean.class);
+
+ Object result = expr.getValue(context).toString();
+ return "true".equals(result);
+ }
+
+ /**
+ * Creates the URI using the link descriptor.
+ *
+ * @param link the link descriptor
+ * @param entity the entity returned from the resource method
+ * @param resource the resource class instance that returned the entity
+ * @param instance the instance that contains the entity, e.g. the value of a field within an entity class.
+ * @param uriInfo JAX-RS {@link UriInfo}
+ * @param rmc the {@link ResourceMappingContext}
+ * @return the URI
+ */
+ static URI buildURI(InjectLinkDescriptor link,
+ Object entity,
+ Object resource,
+ Object instance,
+ UriInfo uriInfo,
+ ResourceMappingContext rmc) {
+
+ String template = link.getLinkTemplate(rmc);
+
+ // first process any embedded EL expressions
+ LinkELContext context = new LinkELContext(entity, resource, instance);
+ ValueExpression expr = expressionFactory.createValueExpression(context,
+ template, String.class);
+ template = expr.getValue(context).toString();
+
+ // now process any embedded URI template parameters
+ UriBuilder ub = applyLinkStyle(template, link.getLinkStyle(), uriInfo);
+ UriTemplateParser parser = new UriTemplateParser(template);
+ List<String> parameterNames = parser.getNames();
+ Map<String, Object> valueMap = getParameterValues(parameterNames, link, context, uriInfo);
+ return ub.buildFromMap(valueMap);
+ }
+
+ private static UriBuilder applyLinkStyle(String template, InjectLink.Style style, UriInfo uriInfo) {
+ UriBuilder ub = null;
+ switch (style) {
+ case ABSOLUTE:
+ ub = uriInfo.getBaseUriBuilder().path(template);
+ break;
+ case ABSOLUTE_PATH:
+ String basePath = uriInfo.getBaseUri().getPath();
+ ub = UriBuilder.fromPath(basePath).path(template);
+ break;
+ case RELATIVE_PATH:
+ ub = UriBuilder.fromPath(template);
+ break;
+ }
+ return ub;
+ }
+
+ private static Map<String, Object> getParameterValues(List<String> parameterNames,
+ InjectLinkDescriptor linkField,
+ LinkELContext context,
+ UriInfo uriInfo) {
+ Map<String, Object> values = new HashMap<>();
+ for (String name : parameterNames) {
+ String elExpression = linkField.getBinding(name);
+ if (elExpression == null) {
+ String value = uriInfo.getPathParameters().getFirst(name);
+ if (value == null) {
+ value = uriInfo.getQueryParameters().getFirst(name);
+ }
+ if (value != null) {
+ values.put(name, value);
+ continue;
+ }
+ elExpression = "${" + ResponseContextResolver.INSTANCE_OBJECT + "." + name + "}";
+ }
+ ValueExpression expr = expressionFactory.createValueExpression(context,
+ elExpression, String.class);
+
+ Object value = expr.getValue(context);
+ values.put(name, value != null ? value.toString() : null);
+ }
+ return values;
+ }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/EntityDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/EntityDescriptor.java
new file mode 100644
index 0000000..314c839
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/EntityDescriptor.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Link;
+
+/**
+ * Describes an entity in terms of its fields, bean properties and {@link InjectLink}
+ * annotated fields.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class EntityDescriptor {
+
+ // Maintains an internal static cache to optimize processing
+ private static final Map<Class<?>, EntityDescriptor> descriptors = new HashMap<>();
+
+ static synchronized EntityDescriptor getInstance(Class<?> entityClass) {
+ if (descriptors.containsKey(entityClass)) {
+ return descriptors.get(entityClass);
+ } else {
+ EntityDescriptor descriptor = new EntityDescriptor(entityClass);
+ descriptors.put(entityClass, descriptor);
+ return descriptor;
+ }
+ }
+
+ // instance
+
+ private Map<String, FieldDescriptor> nonLinkFields;
+ private Map<String, FieldDescriptor> linkFields;
+ private List<LinkHeaderDescriptor> linkHeaders;
+
+ /**
+ * Construct an new descriptor by inspecting the supplied class.
+ *
+ * @param entityClass
+ */
+ private EntityDescriptor(Class<?> entityClass) {
+ // create a list of link headers
+ this.linkHeaders = new ArrayList<>();
+ findLinkHeaders(entityClass);
+ this.linkHeaders = Collections.unmodifiableList(linkHeaders);
+
+ // create a list of field names
+ this.nonLinkFields = new HashMap<>();
+ this.linkFields = new HashMap<>();
+ findFields(entityClass);
+ this.nonLinkFields = Collections.unmodifiableMap(this.nonLinkFields);
+ this.linkFields = Collections.unmodifiableMap(this.linkFields);
+ }
+
+ Collection<FieldDescriptor> getLinkFields() {
+ return linkFields.values();
+ }
+
+ Collection<FieldDescriptor> getNonLinkFields() {
+ return nonLinkFields.values();
+ }
+
+ List<LinkHeaderDescriptor> getLinkHeaders() {
+ return linkHeaders;
+ }
+
+ /**
+ * Find and cache the fields of the supplied class and its superclasses and
+ * interfaces.
+ *
+ * @param entityClass the class
+ */
+ private void findFields(Class<?> entityClass) {
+ for (Field f : entityClass.getDeclaredFields()) {
+ InjectLink a = f.getAnnotation(InjectLink.class);
+ Class<?> t = f.getType();
+ if (a != null) {
+ if (t.equals(String.class) || t.equals(URI.class) || Link.class.isAssignableFrom(t)) {
+ if (!linkFields.containsKey(f.getName())) {
+ linkFields.put(f.getName(), new InjectLinkFieldDescriptor(f, a, t));
+ }
+ } else {
+ // TODO unsupported type
+ }
+ } else if (f.isAnnotationPresent(InjectLinks.class)) {
+
+ if (List.class.isAssignableFrom(t)
+ || t.isArray() && Link.class.isAssignableFrom(t.getComponentType())) {
+
+ InjectLinks a2 = f.getAnnotation(InjectLinks.class);
+ linkFields.put(f.getName(), new InjectLinksFieldDescriptor(f, a2, t));
+ } else {
+ throw new IllegalArgumentException("Can only inject links onto a List<Link> or Link[] object");
+ }
+
+ } else {
+ // see issue http://java.net/jira/browse/JERSEY-625
+ if ((f.getModifiers() & Modifier.STATIC) > 0
+ || f.getName().startsWith("java.")
+ || f.getName().startsWith("javax.")) {
+ continue;
+ }
+ nonLinkFields.put(f.getName(), new FieldDescriptor(f));
+ }
+ }
+
+ // look for nonLinkFields in superclasses
+ Class<?> sc = entityClass.getSuperclass();
+ if (sc != null && sc != Object.class) {
+ findFields(sc);
+ }
+
+ // look for nonLinkFields in interfaces
+ for (Class<?> ic : entityClass.getInterfaces()) {
+ findFields(ic);
+ }
+ }
+
+ private void findLinkHeaders(Class<?> entityClass) {
+ InjectLink linkHeaderAnnotation = entityClass.getAnnotation(InjectLink.class);
+ if (linkHeaderAnnotation != null) {
+ linkHeaders.add(new LinkHeaderDescriptor(linkHeaderAnnotation));
+ }
+ InjectLinks linkHeadersAnnotation = entityClass.getAnnotation(InjectLinks.class);
+ if (linkHeadersAnnotation != null) {
+ for (InjectLink linkHeader : linkHeadersAnnotation.value()) {
+ linkHeaders.add(new LinkHeaderDescriptor(linkHeader));
+ }
+ }
+
+ // look in superclasses
+ Class<?> sc = entityClass.getSuperclass();
+ if (sc != null && sc != Object.class) {
+ findLinkHeaders(sc);
+ }
+
+ // look in interfaces
+ for (Class<?> ic : entityClass.getInterfaces()) {
+ findLinkHeaders(ic);
+ }
+ }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldDescriptor.java
new file mode 100644
index 0000000..da44b68
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldDescriptor.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Utility class for working with class fields
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class FieldDescriptor {
+
+ protected Field field;
+
+ FieldDescriptor(Field f) {
+ this.field = f;
+ }
+
+ Object getFieldValue(Object instance) {
+ setAccessibleField(field);
+ Object value = null;
+ try {
+ value = field.get(instance);
+ } catch (IllegalArgumentException | IllegalAccessException ex) {
+ Logger.getLogger(FieldDescriptor.class.getName()).log(Level.FINE, null, ex);
+ }
+ return value;
+ }
+
+ public String getFieldName() {
+ return field.getName();
+ }
+
+ static void setAccessibleField(final Field f) {
+ if (Modifier.isPublic(f.getModifiers())) {
+ return;
+ }
+
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ if (!f.isAccessible()) {
+ f.setAccessible(true);
+ }
+ return f;
+ }
+ });
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final FieldDescriptor other = (FieldDescriptor) obj;
+ if (this.field != other.field && (this.field == null || !this.field.equals(other.field))) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 83 * hash + (this.field != null ? this.field.hashCode() : 0);
+ return hash;
+ }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldProcessor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldProcessor.java
new file mode 100644
index 0000000..db852dd
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldProcessor.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.reflect.Modifier;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Link;
+import javax.ws.rs.core.UriInfo;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+import org.glassfish.jersey.linking.contributing.ResourceLinkContributionContext;
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+/**
+ * Utility class that can inject links into {@link org.glassfish.jersey.linking.InjectLink} annotated fields in
+ * an entity.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class FieldProcessor<T> {
+
+ private EntityDescriptor instanceDescriptor;
+ private static final Logger log = Logger.getLogger(FieldProcessor.class.getName());
+
+ FieldProcessor(Class<T> c) {
+ instanceDescriptor = EntityDescriptor.getInstance(c);
+ }
+
+ /**
+ * Inject any {@link org.glassfish.jersey.linking.InjectLink} annotated fields in the supplied entity and
+ * recursively process its fields.
+ *
+ * @param entity the entity object returned by the resource method
+ * @param uriInfo the uriInfo for the request
+ * @param rmc the ResourceMappingContext used for building URIs
+ * @param rlcc the ResourceLinkContributionContext used to find link contributors
+ */
+ void processLinks(T entity, UriInfo uriInfo, ResourceMappingContext rmc, ResourceLinkContributionContext rlcc) {
+ Set<Object> processed = new HashSet<Object>();
+ Object resource = uriInfo.getMatchedResources().get(0);
+ processLinks(entity, resource, entity, processed, uriInfo, rmc, rlcc);
+ }
+
+ /**
+ * Inject any {@link org.glassfish.jersey.linking.InjectLink} annotated fields in the supplied instance. Called
+ * once for the entity and then recursively for each member and field.
+ *
+ * @param entity the entity object returned by the resource method
+ * @param processed a list of already processed objects, used to break
+ * recursion when processing circular references.
+ * @param uriInfo the uriInfo for the request
+ * @param rmc the ResourceMappingContext used for building URIs
+ * @param rlcc the ResourceLinkContributionContext used to find link contributors
+ */
+ private void processLinks(Object entity, Object resource, Object instance,
+ Set<Object> processed, UriInfo uriInfo,
+ ResourceMappingContext rmc, ResourceLinkContributionContext rlcc) {
+
+ try {
+ if (instance == null || processed.contains(instance)) {
+ return; // ignore null properties and defeat circular references
+ }
+ if (instance.getClass().getName().startsWith("java.lang")) {
+ return;
+ }
+ processed.add(instance);
+ } catch (RuntimeException e) {
+ // fix for JERSEY-1656
+ log.log(Level.INFO, LinkMessages.WARNING_LINKFILTER_PROCESSING(instance.getClass().getName()), e);
+ }
+
+ // Process any @Link annotated fields in entity
+ for (FieldDescriptor field : instanceDescriptor.getLinkFields()) {
+
+ // TODO replace with properly poly-morphic code
+ if (field instanceof InjectLinkFieldDescriptor) {
+ InjectLinkFieldDescriptor linkField = (InjectLinkFieldDescriptor) field;
+ if (ELLinkBuilder.evaluateCondition(linkField.getCondition(), entity, resource, instance)) {
+ URI uri = ELLinkBuilder.buildURI(linkField, entity, resource, instance, uriInfo, rmc);
+ linkField.setPropertyValue(instance, uri);
+ }
+ } else if (field instanceof InjectLinksFieldDescriptor) {
+
+ InjectLinksFieldDescriptor linksField = (InjectLinksFieldDescriptor) field;
+ List<Link> list = new ArrayList<>();
+ for (InjectLinkFieldDescriptor linkField : linksField.getLinksToInject()) {
+ if (ELLinkBuilder.evaluateCondition(linkField.getCondition(), entity, resource, instance)) {
+ URI uri = ELLinkBuilder.buildURI(linkField, entity, resource, instance, uriInfo, rmc);
+ Link link = linkField.getLink(uri);
+ list.add(link);
+ }
+ }
+ List<ProvideLinkDescriptor> linkContributors = rlcc.getContributorsFor(instance.getClass());
+ for (ProvideLinkDescriptor linkContributor : linkContributors) {
+ if (ELLinkBuilder.evaluateCondition(linkContributor.getCondition(),
+ entity, linkContributor.getResource(), instance)) {
+ URI uri = ELLinkBuilder.buildURI(linkContributor, entity, resource, instance, uriInfo, rmc);
+ Link link = linkContributor.getLink(uri);
+ list.add(link);
+ }
+ }
+
+ linksField.setPropertyValue(instance, list);
+ }
+ }
+
+ // If entity is an array, collection, or map then process members
+ Class<?> instanceClass = instance.getClass();
+ if (instanceClass.isArray() && Object[].class.isAssignableFrom(instanceClass)) {
+ Object array[] = (Object[]) instance;
+ for (Object member : array) {
+ processMember(entity, resource, member, processed, uriInfo, rmc, rlcc);
+ }
+ } else if (instance instanceof Iterable) {
+ Iterable iterable = (Iterable) instance;
+ for (Object member : iterable) {
+ processMember(entity, resource, member, processed, uriInfo, rmc, rlcc);
+ }
+ } else if (instance instanceof Map) {
+ Map map = (Map) instance;
+ for (Object member : map.entrySet()) {
+ processMember(entity, resource, member, processed, uriInfo, rmc, rlcc);
+ }
+ }
+
+ // Recursively process all member fields
+ for (FieldDescriptor member : instanceDescriptor.getNonLinkFields()) {
+
+ if (fieldSuitableForIntrospection(member)) {
+ processMember(entity, resource, member.getFieldValue(instance), processed, uriInfo, rmc, rlcc);
+ }
+ }
+
+ }
+
+ private boolean fieldSuitableForIntrospection(FieldDescriptor member) {
+ return member.field == null
+ || (!member.field.isSynthetic()
+ && !Modifier.isTransient(member.field.getModifiers())
+ && !member.field.getType().isPrimitive()
+ && member.field.getType() != String.class
+ && !member.field.isAnnotationPresent(InjectLinkNoFollow.class)
+ && !member.field.isAnnotationPresent(XmlTransient.class));
+ }
+
+ private void processMember(Object entity, Object resource, Object member, Set<Object> processed, UriInfo uriInfo,
+ ResourceMappingContext rmc, ResourceLinkContributionContext rlcc) {
+ if (member != null) {
+ FieldProcessor<?> proc = new FieldProcessor(member.getClass());
+ proc.processLinks(entity, resource, member, processed, uriInfo, rmc, rlcc);
+ }
+ }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/HeaderProcessor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/HeaderProcessor.java
new file mode 100644
index 0000000..770fada
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/HeaderProcessor.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+/**
+ * Processes @Link and @LinkHeaders annotations on entity classes and
+ * adds appropriate HTTP Link headers.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class HeaderProcessor<T> {
+
+ private EntityDescriptor instanceDescriptor;
+
+ HeaderProcessor(Class<T> c) {
+ instanceDescriptor = EntityDescriptor.getInstance(c);
+ }
+
+ /**
+ * Process any {@link InjectLink} annotations on the supplied entity.
+ * @param entity the entity object returned by the resource method
+ * @param uriInfo the uriInfo for the request
+ * @param headers the map into which the headers will be added
+ */
+ void processLinkHeaders(T entity,
+ UriInfo uriInfo,
+ ResourceMappingContext rmc,
+ MultivaluedMap<String, Object> headers) {
+ List<String> headerValues = getLinkHeaderValues(entity, uriInfo, rmc);
+ for (String headerValue : headerValues) {
+ headers.add("Link", headerValue);
+ }
+ }
+
+ List<String> getLinkHeaderValues(Object entity, UriInfo uriInfo, ResourceMappingContext rmc) {
+ final List<Object> matchedResources = uriInfo.getMatchedResources();
+
+ if (!matchedResources.isEmpty()) {
+ final Object resource = matchedResources.get(0);
+ final List<String> headerValues = new ArrayList<>();
+
+ for (LinkHeaderDescriptor desc : instanceDescriptor.getLinkHeaders()) {
+ if (ELLinkBuilder.evaluateCondition(desc.getCondition(), entity, resource, entity)) {
+ String headerValue = getLinkHeaderValue(desc, entity, resource, uriInfo, rmc);
+ headerValues.add(headerValue);
+ }
+ }
+ return headerValues;
+ }
+
+ return Collections.emptyList();
+ }
+
+ private static String getLinkHeaderValue(LinkHeaderDescriptor desc, Object entity, Object resource, UriInfo uriInfo,
+ ResourceMappingContext rmc) {
+ URI uri = ELLinkBuilder.buildURI(desc, entity, resource, entity, uriInfo, rmc);
+ InjectLink link = desc.getLinkHeader();
+ return InjectLink.Util.buildLinkFromUri(uri, link).toString();
+ }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLink.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLink.java
new file mode 100644
index 0000000..a42b3e9
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLink.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.net.URI;
+
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.Beta;
+
+/**
+ * Specifies a link injection target in a returned representation bean. May be
+ * used on fields of type String or URI. One of {@link #value()} or
+ * {@link #resource()} must be specified.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+@Target({ElementType.FIELD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Beta
+public @interface InjectLink {
+
+ /**
+ * Styles of URI supported
+ */
+ enum Style {
+
+ /**
+ * An absolute URI. The URI template will be prefixed with the absolute
+ * base URI of the application.
+ */
+ ABSOLUTE,
+ /**
+ * An absolute path. The URI template will be prefixed with the absolute
+ * base path of the application.
+ */
+ ABSOLUTE_PATH,
+ /**
+ * A relative path. The URI template will be converted to a relative
+ * path with no prefix.
+ */
+ RELATIVE_PATH
+
+ }
+
+ /**
+ * The style of URI to inject
+ */
+ Style style() default Style.ABSOLUTE_PATH;
+
+ /**
+ * Specifies a URI template that will be used to build the injected URI. The
+ * template may contain both URI template parameters (e.g. {id}) and EL
+ * expressions (e.g. ${instance.id}) using the same implicit beans as
+ * {@link Binding#value()}. URI template parameter values are resolved as
+ * described in {@link #resource()}. E.g. the following three alternatives
+ * are equivalent:
+ * <pre>
+ * @Ref("{id}")
+ * @Ref(value="{id}", bindings={
+ * @Binding(name="id" value="${instance.id}"}
+ * )
+ * @Ref("${instance.id}")
+ * </pre>
+ */
+ String value() default "";
+
+ /**
+ * Specifies a resource class whose @Path URI template will be used to build
+ * the injected URI. Embedded URI template parameter values are resolved as
+ * follows:
+ * <ol>
+ * <li>If the {@link #bindings()} property contains a binding specification
+ * for the parameter then that is used</li>
+ * <li>Otherwise an implicit binding is used that extracts the value of a
+ * bean property by the same name as the URI template from the implicit
+ * {@code instance} bean (see {@link Binding}).</li>
+ * </ol>
+ * <p>
+ * E.g. assuming a resource class {@code SomeResource} with the
+ * following {@code @Path("{id}")} annotation, the following two
+ * alternatives are therefore equivalent:</p>
+ * <pre>
+ * @Ref(resource=SomeResource.class)
+ * @Ref(resource=SomeResource.class, bindings={
+ * @Binding(name="id" value="${instance.id}"}
+ * )
+ * </pre>
+ */
+ Class<?> resource() default Class.class;
+
+ /**
+ * Used in conjunction with {@link #resource()} to specify a subresource
+ * locator or method. The value is the name of the method. The value of the
+ * method's @Path annotation will be appended to the value of the
+ * class-level @Path annotation separated by '/' if necessary.
+ */
+ String method() default "";
+
+ /**
+ * Specifies the bindings for embedded URI template parameters.
+ *
+ * @see Binding
+ */
+ Binding[] bindings() default {};
+
+ /**
+ * Specifies a boolean EL expression whose value determines whether a Ref is
+ * set (true) or not (false). Omission of a condition will always insert a
+ * ref.
+ */
+ String condition() default "";
+
+ // Link properties
+ //
+
+ /**
+ * Specifies the relationship.
+ */
+ String rel() default "";
+
+ /**
+ * Specifies the reverse relationship.
+ */
+ String rev() default "";
+
+ /**
+ * Specifies the media type.
+ */
+ String type() default "";
+
+ /**
+ * Specifies the title.
+ */
+ String title() default "";
+
+ /**
+ * Specifies the anchor
+ */
+ String anchor() default "";
+
+ /**
+ * Specifies the media
+ */
+ String media() default "";
+
+ /**
+ * Specifies the lang of the referenced resource
+ */
+ String hreflang() default "";
+
+ /**
+ * Specifies extension parameters as name-value pairs.
+ */
+ Extension[] extensions() default {};
+
+ @Target({ElementType.TYPE, ElementType.FIELD})
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface Extension {
+
+ /**
+ * Specifies the name of the extension parameter
+ */
+ String name();
+
+ /**
+ * Specifies the value of the extension parameter
+ */
+ String value();
+ }
+
+ class Util {
+
+ public static Link buildLinkFromUri(URI uri, InjectLink link) {
+
+ javax.ws.rs.core.Link.Builder builder = javax.ws.rs.core.Link.fromUri(uri);
+ if (!link.rel().isEmpty()) {
+ builder = builder.rel(link.rel());
+ }
+ if (!link.rev().isEmpty()) {
+ builder = builder.param("rev", link.rev());
+ }
+ if (!link.type().isEmpty()) {
+ builder = builder.type(link.type());
+ }
+ if (!link.title().isEmpty()) {
+ builder = builder.param("title", link.title());
+ }
+ if (!link.anchor().isEmpty()) {
+ builder = builder.param("anchor", link.anchor());
+ }
+ if (!link.media().isEmpty()) {
+ builder = builder.param("media", link.media());
+ }
+ if (!link.hreflang().isEmpty()) {
+ builder = builder.param("hreflang", link.hreflang());
+ }
+ for (InjectLink.Extension ext : link.extensions()) {
+ builder = builder.param(ext.name(), ext.value());
+ }
+ return builder.build();
+ }
+ }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkDescriptor.java
new file mode 100644
index 0000000..4ee06ff
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkDescriptor.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+/**
+ * Utility for working with @Ref annotations
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+interface InjectLinkDescriptor {
+
+ /**
+ * Get the style
+ * @return the style
+ */
+ InjectLink.Style getLinkStyle();
+
+ /**
+ * Get the link template, either directly from the value() or from the
+ * @Path of the class referenced in resource()
+ * @return the link template
+ */
+ String getLinkTemplate(ResourceMappingContext rmc);
+
+ /**
+ * Get the binding as an EL expression for a particular URI template parameter
+ * @param name
+ * @return the EL binding
+ */
+ String getBinding(String name);
+
+ /**
+ * Get the condition.
+ * @return the condition
+ */
+ String getCondition();
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkFieldDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkFieldDescriptor.java
new file mode 100644
index 0000000..d6e36f6
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkFieldDescriptor.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+import org.glassfish.jersey.server.model.AnnotatedMethod;
+import org.glassfish.jersey.server.model.MethodList;
+
+/**
+ * Utility class for working with {@link InjectLink} annotated fields.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class InjectLinkFieldDescriptor extends FieldDescriptor implements InjectLinkDescriptor {
+
+ private InjectLink link;
+ private Class<?> type;
+ private Map<String, String> bindings;
+
+ /**
+ * C'tor
+ *
+ * @param f the field to inject
+ * @param l the InjectLink annotation
+ * @param t the class that contains field f
+ */
+ InjectLinkFieldDescriptor(Field f, InjectLink l, Class<?> t) {
+ super(f);
+ link = l;
+ type = t;
+ bindings = new HashMap<>();
+ for (Binding binding : l.bindings()) {
+ bindings.put(binding.name(), binding.value());
+ }
+ }
+
+ /**
+ * Injects the uri into the field.
+ *
+ * @param instance the target for the injection
+ * @param uri the value to inject
+ */
+ void setPropertyValue(Object instance, URI uri) {
+ setAccessibleField(field);
+ try {
+
+ Object value;
+ if (Objects.equals(URI.class, type)) {
+ value = uri;
+ } else if (Link.class.isAssignableFrom(type)) {
+
+ // Make a link with the correct bindings
+ value = getLink(uri);
+ } else if (Objects.equals(String.class, type)) {
+ value = uri.toString();
+ } else {
+ throw new IllegalArgumentException("Field type " + type + " not one of supported String,URI and Link");
+ }
+
+ field.set(instance, value);
+ } catch (IllegalArgumentException | IllegalAccessException ex) {
+ Logger.getLogger(InjectLinkFieldDescriptor.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ /**
+ * Simple delegate to {@link InjectLink#style()}
+ * @return {@link InjectLink#style()}
+ */
+ @Override
+ public InjectLink.Style getLinkStyle() {
+ return link.style();
+ }
+
+ /**
+ * Returns the template based on the {@link ResourceMappingContext}
+ *
+ * @param rmc the context
+ * @return the link template
+ */
+ @Override
+ public String getLinkTemplate(ResourceMappingContext rmc) {
+ return getLinkTemplate(rmc, link);
+ }
+
+
+ /**
+ * Returns the template based on the {@link ResourceMappingContext}
+ *
+ * @param rmc the context
+ * @param link the link
+ * @return the link template
+ */
+ static String getLinkTemplate(ResourceMappingContext rmc, InjectLink link) {
+ String template = null;
+ if (Objects.equals(link.resource(), Class.class)) {
+ template = link.value();
+ } else {
+ ResourceMappingContext.Mapping map = rmc.getMapping(link.resource());
+ if (map != null) {
+ template = map.getTemplate().getTemplate();
+ } else {
+ // extract template from specified class' @Path annotation
+ Path path = link.resource().getAnnotation(Path.class);
+ template = path == null ? "" : path.value();
+ }
+
+ // extract template from specified class' @Path annotation
+ if (!link.method().isEmpty()) {
+ // append value of method's @Path annotation
+ MethodList methods = new MethodList(link.resource());
+ methods = methods.withMetaAnnotation(HttpMethod.class);
+ for (AnnotatedMethod method : methods) {
+ if (!Objects.equals(method.getMethod().getName(), link.method())) {
+ continue;
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append(template);
+
+ Path methodPath = method.getAnnotation(Path.class);
+ if (methodPath != null) {
+ String methodTemplate = methodPath.value();
+
+ if (!(template.endsWith("/") || methodTemplate.startsWith("/"))) {
+ builder.append("/");
+ }
+ builder.append(methodTemplate);
+ }
+
+ CharSequence querySubString = extractQueryParams(method);
+
+ if (querySubString.length() > 0) {
+ builder.append("{?");
+ builder.append(querySubString);
+ builder.append("}");
+ }
+
+ template = builder.toString();
+ break;
+ }
+ }
+ }
+
+ return template;
+ }
+
+ static StringBuilder extractQueryParams(AnnotatedMethod method) throws SecurityException {
+ // append query parameters
+ StringBuilder querySubString = new StringBuilder();
+ int parameterIndex = 0;
+ for (Annotation[] paramAnns : method.getParameterAnnotations()) {
+ for (Annotation ann : paramAnns) {
+ if (Objects.equals(ann.annotationType(), QueryParam.class)) {
+ querySubString.append(((QueryParam) ann).value());
+ querySubString.append(',');
+ }
+ if (Objects.equals(ann.annotationType(), BeanParam.class)) {
+ Class<?> beanParamType = method.getParameterTypes()[parameterIndex];
+ Field[] fields = beanParamType.getFields();
+ for (Field field : fields) {
+ QueryParam queryParam = field.getAnnotation(QueryParam.class);
+ if (queryParam != null) {
+ querySubString.append(queryParam.value());
+ querySubString.append(',');
+ }
+ }
+ Method[] beanMethods = beanParamType.getMethods();
+ for (Method beanMethod : beanMethods) {
+ QueryParam queryParam = beanMethod.getAnnotation(QueryParam.class);
+ if (queryParam != null) {
+ querySubString.append(queryParam.value());
+ querySubString.append(',');
+ }
+ }
+ }
+ }
+ parameterIndex++;
+ }
+
+ return querySubString;
+ }
+
+ /**
+ * Creates a link from uri combined with {@link InjectLink}.
+ *
+ * @param uri uri for the link
+ * @return Link instance
+ */
+ Link getLink(URI uri) {
+ return InjectLink.Util.buildLinkFromUri(uri, link);
+ }
+
+ /**
+ * Gets the binding by name
+ *
+ * @param name name of the binding
+ * @return the binding
+ */
+ public String getBinding(String name) {
+ return bindings.get(name);
+ }
+
+
+ /**
+ * Returns the condition of {@link InjectLink}.
+ * @return {@link InjectLink#condition()}
+ */
+ public String getCondition() {
+ return link.condition();
+ }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkNoFollow.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkNoFollow.java
new file mode 100644
index 0000000..bb83c56
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkNoFollow.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.glassfish.jersey.Beta;
+
+/**
+ * Specifies on a field that should be ignored by Link recursive introspection.
+ *
+ * In some scenarios like with framework contexts or for more control,
+ * it is not wanted to try to find nested links into entities or collections.
+ *
+ * @author Aurelien Thieriot
+ */
+@Target({ElementType.FIELD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Beta
+public @interface InjectLinkNoFollow {
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinks.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinks.java
new file mode 100644
index 0000000..6257537
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinks.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.Beta;
+
+/**
+ * Used to request the addition of a set of links, can be used for both
+ * link headers on a Class or injection into a List<Link> or Link[] property.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+@Target({ElementType.TYPE, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Beta
+public @interface InjectLinks {
+
+ /**
+ * Container for a set of {@link org.glassfish.jersey.linking.InjectLink} annotations
+ * @return array of {@code InjectLink} elements
+ */
+ InjectLink[] value() default {};
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinksFieldDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinksFieldDescriptor.java
new file mode 100644
index 0000000..7f4925c
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinksFieldDescriptor.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Link;
+
+/**
+ * Utility class for working with {@link InjectLinks} annotated fields.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class InjectLinksFieldDescriptor extends FieldDescriptor {
+
+ private final InjectLinks link;
+ private final Class<?> type;
+
+ /**
+ * C'tor
+ *
+ * @param f the field to inject
+ * @param l the InjectLinks annotation
+ * @param t the class that contains field f
+ */
+ InjectLinksFieldDescriptor(Field f, InjectLinks l, Class<?> t) {
+ super(f);
+ link = l;
+ type = t;
+ }
+
+ /**
+ * Injects the Link list into the instance.
+ *
+ * If the field is {@code null} then it is replaced with the list.
+ * If the field already contains links, then the content is merged
+ * with this list into a new list and injected.
+ *
+ * @param instance the instance that contains the field f
+ * @param list the list of links to inject
+ */
+ public void setPropertyValue(Object instance, List<Link> list) {
+ setAccessibleField(field);
+ try {
+ List<Link> merged = mergeWithExistingField(instance, list);
+
+ Object value;
+ if (Objects.equals(List.class, type)) {
+ value = merged;
+ } else if (type.isArray()) {
+ value = merged.toArray((Object[]) Array.newInstance(type.getComponentType(), merged.size()));
+ } else {
+ throw new IllegalArgumentException("Field type " + type + " not one of supported List<Link> or Link[]");
+ }
+
+ field.set(instance, value);
+
+
+ } catch (IllegalArgumentException | IllegalAccessException ex) {
+ Logger.getLogger(InjectLinksFieldDescriptor.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ private List<Link> mergeWithExistingField(Object instance, List<Link> list) throws IllegalAccessException {
+ Object existing = field.get(instance);
+ if (existing != null) {
+ if (Collection.class.isAssignableFrom(existing.getClass()) && !((Collection) existing).isEmpty()) {
+ List<Link> merged = new ArrayList<>(list);
+ merged.addAll((Collection<Link>) existing);
+ return merged;
+ } else if (existing.getClass().isArray() && existing.getClass().isAssignableFrom(Link[].class)) {
+ List<Link> merged = new ArrayList<>(list);
+ merged.addAll(Arrays.asList((Link[]) existing));
+ return merged;
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Creates {@link InjectLinkFieldDescriptor} for each link to inject.
+ */
+ InjectLinkFieldDescriptor[] getLinksToInject() {
+ final InjectLink[] listOfLinks = link.value();
+ InjectLinkFieldDescriptor[] fields = new InjectLinkFieldDescriptor[listOfLinks.length];
+ for (int i = 0; i < fields.length; i++) {
+ fields[i] = new InjectLinkFieldDescriptor(field, listOfLinks[i], Link.class);
+ }
+ return fields;
+ }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkELContext.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkELContext.java
new file mode 100644
index 0000000..4af9916
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkELContext.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import javax.el.BeanELResolver;
+import javax.el.CompositeELResolver;
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.FunctionMapper;
+import javax.el.VariableMapper;
+
+/**
+ * An ELContext that encapsulates the response information for use by the
+ * expression evaluator.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class LinkELContext extends ELContext {
+
+ private Object entity;
+ private Object resource;
+ private Object instance;
+
+ /**
+ * Convenience constructor for the common case where a context where
+ * the entity and instance are the same. Equivalent to
+ * {@link #LinkELContext(Object, Object, Object)}.
+ *
+ * @param entity
+ * @param resource
+ */
+ LinkELContext(Object entity, Object resource) {
+ this.entity = entity;
+ this.resource = resource;
+ this.instance = entity;
+ }
+
+ /**
+ * Construct a new context
+ * @param entity the entity returned from the resource method
+ * @param resource the resource class instance that returned the entity
+ * @param instance the instance that contains the entity, e.g. the value of
+ * a field within an entity class.
+ */
+ LinkELContext(Object entity, Object resource, Object instance) {
+ this.entity = entity;
+ this.resource = resource;
+ this.instance = instance;
+ }
+
+ @Override
+ public ELResolver getELResolver() {
+ CompositeELResolver resolver = new CompositeELResolver();
+ resolver.add(new ResponseContextResolver(entity, resource, instance));
+ resolver.add(new BeanELResolver(true));
+ return resolver;
+ }
+
+ @Override
+ public FunctionMapper getFunctionMapper() {
+ return null;
+ }
+
+ @Override
+ public VariableMapper getVariableMapper() {
+ return null;
+ }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkHeaderDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkHeaderDescriptor.java
new file mode 100644
index 0000000..7e621ad
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkHeaderDescriptor.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.glassfish.jersey.linking.InjectLink.Style;
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+/**
+ * Utility class for working with {@link org.glassfish.jersey.linking.InjectLink} annotations.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class LinkHeaderDescriptor implements InjectLinkDescriptor {
+
+ private InjectLink linkHeader;
+ private Map<String, String> bindings;
+
+ LinkHeaderDescriptor(InjectLink linkHeader) {
+ this.linkHeader = linkHeader;
+ bindings = new HashMap<>();
+ for (Binding binding : linkHeader.bindings()) {
+ bindings.put(binding.name(), binding.value());
+ }
+ }
+
+ InjectLink getLinkHeader() {
+ return linkHeader;
+ }
+
+ public String getLinkTemplate(ResourceMappingContext rmc) {
+ return InjectLinkFieldDescriptor.getLinkTemplate(rmc, linkHeader);
+ }
+
+ public Style getLinkStyle() {
+ return linkHeader.style();
+ }
+
+ public String getBinding(String name) {
+ return bindings.get(name);
+ }
+
+ public String getCondition() {
+ return linkHeader.condition();
+ }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkMessages.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkMessages.java
new file mode 100644
index 0000000..78fae95
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkMessages.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import org.glassfish.jersey.internal.l10n.Localizable;
+import org.glassfish.jersey.internal.l10n.LocalizableMessageFactory;
+import org.glassfish.jersey.internal.l10n.Localizer;
+
+/**
+ * Message for declarative linking
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class LinkMessages {
+
+ private static final LocalizableMessageFactory messageFactory = new LocalizableMessageFactory(
+ "org.glassfish.jersey.media.linking.internal");
+ private static final Localizer localizer = new Localizer();
+
+ private static Localizable localizableWARNING_LINKFILTER_PROCESSING(Object arg0) {
+ return messageFactory.getMessage("warning.linkfilter.processing", arg0);
+ }
+
+ /**
+ * LinkFilter cannot process class {0}, exception occurred during processing. Class will be ignored in the LinkFilter.
+ *
+ */
+ static String WARNING_LINKFILTER_PROCESSING(Object arg0) {
+ return localizer.localize(localizableWARNING_LINKFILTER_PROCESSING(arg0));
+ }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLink.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLink.java
new file mode 100644
index 0000000..3c00ba0
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLink.java
@@ -0,0 +1,188 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.net.URI;
+
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.Beta;
+
+/**
+ * Use this on resource methods to contribute links to a representation.
+ *
+ * It is the inverse of {@link InjectLink} instead of annotating the target you annotate the source of the links.
+ * The added benefit is that since you annotate the method you don't need to specify the path to it.
+ *
+ * <p>
+ * <pre>
+ * @ProvideLink(value = Order.class, rel = "self", bindings = @Binding(name = "orderId", value = "${instance.id}"))
+ * @ProvideLink(value = PaymentConfirmation.class, rel = "order",
+ * bindings = @Binding(name = "orderId", value = "${instance.orderId}"))
+ * public Response get(@PathParam("orderId") String orderId) { ...
+ * </pre>
+ * </p>
+ *
+ * It can also be used as a meta annotation, see the Javadoc of {@link InheritFromAnnotation} for details.
+ *
+ * @author Leonard Brünings
+ */
+@Target({ ElementType.METHOD, ElementType.TYPE})
+@Repeatable(ProvideLinks.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Beta
+public @interface ProvideLink {
+
+ /**
+ * The style of URI to inject
+ */
+ InjectLink.Style style() default InjectLink.Style.ABSOLUTE_PATH;
+
+ /**
+ * Provide links for representation classes listed here.
+ *
+ * May use {@link InheritFromAnnotation} for Meta-Annotations
+ */
+ Class<?>[] value();
+
+ /**
+ * Specifies the bindings for embedded URI template parameters.
+ *
+ * @see Binding
+ */
+ Binding[] bindings() default {};
+
+ /**
+ * Specifies a boolean EL expression whose value determines whether a Ref is
+ * set (true) or not (false). Omission of a condition will always insert a
+ * ref.
+ */
+ String condition() default "";
+
+ //
+ // Link properties
+ //
+
+ /**
+ * Specifies the relationship.
+ */
+ String rel() default "";
+
+ /**
+ * Specifies the reverse relationship.
+ */
+ String rev() default "";
+
+ /**
+ * Specifies the media type.
+ */
+ String type() default "";
+
+ /**
+ * Specifies the title.
+ */
+ String title() default "";
+
+ /**
+ * Specifies the anchor
+ */
+ String anchor() default "";
+
+ /**
+ * Specifies the media
+ */
+ String media() default "";
+
+ /**
+ * Specifies the lang of the referenced resource
+ */
+ String hreflang() default "";
+
+ /**
+ * Specifies extension parameters as name-value pairs.
+ */
+ InjectLink.Extension[] extensions() default {};
+
+
+ class Util {
+
+ static Link buildLinkFromUri(URI uri, ProvideLink link) {
+
+ javax.ws.rs.core.Link.Builder builder = javax.ws.rs.core.Link.fromUri(uri);
+ if (!link.rel().isEmpty()) {
+ builder = builder.rel(link.rel());
+ }
+ if (!link.rev().isEmpty()) {
+ builder = builder.param("rev", link.rev());
+ }
+ if (!link.type().isEmpty()) {
+ builder = builder.type(link.type());
+ }
+ if (!link.title().isEmpty()) {
+ builder = builder.param("title", link.title());
+ }
+ if (!link.anchor().isEmpty()) {
+ builder = builder.param("anchor", link.anchor());
+ }
+ if (!link.media().isEmpty()) {
+ builder = builder.param("media", link.media());
+ }
+ if (!link.hreflang().isEmpty()) {
+ builder = builder.param("hreflang", link.hreflang());
+ }
+ for (InjectLink.Extension ext : link.extensions()) {
+ builder = builder.param(ext.name(), ext.value());
+ }
+ return builder.build();
+ }
+ }
+
+ /**
+ * Special interface to indicate that the target should be inherited from the annotated annotation.
+ * <p>
+ * <pre>
+ * @ProvideLinks({
+ * @ProvideLink(value = ProvideLink.InheritFromAnnotation.class, rel = "next", bindings = {
+ * @Binding(name = "page", value = "${instance.number + 1}"),
+ * @Binding(name =@ "size", value = "${instance.size}"),
+ * },
+ * condition = "${instance.nextPageAvailable}"),
+ * @ProvideLink(value = ProvideLink.InheritFromAnnotation.class, rel = "prev", bindings = {
+ * @Binding(name = "page", value = "${instance.number - 1}"),
+ * @Binding(name = "size", value = "${instance.size}"),
+ * },
+ * condition = "${instance.previousPageAvailable}")
+ * })
+ * @Target({ElementType.METHOD})
+ * @Retention(RetentionPolicy.RUNTIME)
+ * @Documented
+ * public @interface PageLinks {
+ * Class<?> value();
+ * }
+ * </pre>
+ * </p>
+ * <p>
+ * In this case the value of each {@link ProvideLink} will be the same as {@code PageLinks} value.
+ * </p>
+ */
+ interface InheritFromAnnotation{}
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLinkDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLinkDescriptor.java
new file mode 100644
index 0000000..a4b6356
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLinkDescriptor.java
@@ -0,0 +1,164 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.annotation.Annotation;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+import org.glassfish.jersey.server.model.AnnotatedMethod;
+import org.glassfish.jersey.server.model.ResourceMethod;
+
+/**
+ * Utility to work with {@link ProvideLink} annotations.
+ *
+ * @author Leonard Brünings
+ */
+public class ProvideLinkDescriptor implements InjectLinkDescriptor {
+ private final ProvideLink provideLink;
+ private final ResourceMethod resource;
+
+ private final Annotation parentAnnotation;
+
+ private final Map<String, String> bindings;
+
+ /**
+ * c'tor
+ *
+ * @param resource the annotated resource method
+ * @param provideLink the annotaion
+ * @param parentAnnotation the parent annotation if present or {@code null}
+ */
+ public ProvideLinkDescriptor(ResourceMethod resource, ProvideLink provideLink, Annotation parentAnnotation) {
+ this.provideLink = provideLink;
+ this.resource = resource;
+ this.parentAnnotation = parentAnnotation;
+ bindings = new HashMap<>();
+ for (Binding binding : provideLink.bindings()) {
+ bindings.put(binding.name(), binding.value());
+ }
+ }
+
+ /**
+ * @return the annotation
+ */
+ public ProvideLink getProvideLink() {
+ return provideLink;
+ }
+
+ /**
+ * @return the annotated resource method
+ */
+ public ResourceMethod getResource() {
+ return resource;
+ }
+
+ /**
+ * Get the style
+ *
+ * @return the style
+ */
+ public InjectLink.Style getLinkStyle() {
+ return provideLink.style();
+ }
+
+ /**
+ * Get the link template, either directly from the value() or from the
+ * {@code @Path} of the class referenced in resource()
+ *
+ * @return the link template
+ */
+ @Override
+ public String getLinkTemplate(ResourceMappingContext rmc) {
+ String template = null;
+ ResourceMappingContext.Mapping map = rmc.getMapping(resource.getInvocable().getHandler().getHandlerClass());
+ if (map != null) {
+ template = map.getTemplate().getTemplate();
+ } else {
+ // extract template from specified class' @Path annotation
+ Path path = resource.getInvocable().getHandler().getHandlerClass().getAnnotation(Path.class);
+ template = path == null ? "" : path.value();
+ }
+ StringBuilder builder = new StringBuilder(template);
+
+ Path methodPath = resource.getInvocable().getDefinitionMethod().getAnnotation(Path.class);
+ if (methodPath != null) {
+ String methodTemplate = methodPath.value();
+
+ if (!(template.endsWith("/") || methodTemplate.startsWith("/"))) {
+ builder.append("/");
+ }
+ builder.append(methodTemplate);
+ }
+
+ CharSequence querySubString = InjectLinkFieldDescriptor.extractQueryParams(
+ new AnnotatedMethod(resource.getInvocable().getDefinitionMethod()));
+
+ if (querySubString.length() > 0) {
+ builder.append("{?");
+ builder.append(querySubString);
+ builder.append("}");
+ }
+
+ template = builder.toString();
+
+ return template;
+ }
+
+ /**
+ * Get the binding as an EL expression for a particular URI template parameter
+ *
+ * @param name binding name.
+ * @return the EL binding.
+ */
+ @Override
+ public String getBinding(String name) {
+ return bindings.get(name);
+ }
+
+ /**
+ * Get the condition.
+ *
+ * @return the condition
+ */
+ @Override
+ public String getCondition() {
+ return provideLink.condition();
+ }
+
+ /**
+ * Builds a link from a {@link URI}.
+ *
+ * @param uri base URI
+ * @return the {@link Link} instance
+ */
+ public Link getLink(URI uri) {
+ return ProvideLink.Util.buildLinkFromUri(uri, provideLink);
+ }
+
+ /**
+ * @return the parent annotation or {@code null}
+ */
+ public Annotation getParentAnnotation() {
+ return parentAnnotation;
+ }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLinks.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLinks.java
new file mode 100644
index 0000000..9e752ee
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLinks.java
@@ -0,0 +1,41 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.Beta;
+
+/**
+ * Container for repeatable annotation, see {@link ProvideLink} for details.
+ *
+ * @author Leonard Brünings
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Beta
+public @interface ProvideLinks {
+
+ /**
+ * Container for a set of {@link ProvideLink} annotations
+ * @return array of {@code ProvideLink} elements
+ */
+ ProvideLink[] value() default {};
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/RequestLinkFilter.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/RequestLinkFilter.java
new file mode 100644
index 0000000..0e234fa
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/RequestLinkFilter.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.io.IOException;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.server.ExtendedUriInfo;
+
+/**
+ * Filter that processes {@link Link} annotated fields in returned response
+ * entities.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ * @see Link
+ */
+class RequestLinkFilter implements ContainerRequestFilter {
+
+ @Context
+ private ExtendedUriInfo uriInfo;
+
+ @Override
+ public void filter(ContainerRequestContext requestContext) throws IOException {
+
+ }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseContextResolver.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseContextResolver.java
new file mode 100644
index 0000000..48f8546
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseContextResolver.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.beans.FeatureDescriptor;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.PropertyNotWritableException;
+
+/**
+ * The initial context resolver that resolves the entity and resource
+ * objects used at the start of an EL expression.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class ResponseContextResolver extends ELResolver {
+
+ private Map<String, Object> responseObjects;
+ private static final String ENTITY_OBJECT = "entity";
+ private static final String RESOURCE_OBJECT = "resource";
+ static final String INSTANCE_OBJECT = "instance";
+
+ ResponseContextResolver(Object entity, Object resource, Object instance) {
+ responseObjects = new HashMap<>();
+ responseObjects.put(ENTITY_OBJECT, entity);
+ responseObjects.put(RESOURCE_OBJECT, resource);
+ responseObjects.put(INSTANCE_OBJECT, instance);
+ }
+
+ private boolean isHandled(ELContext elc, Object base, Object property) {
+ if (base != null) {
+ return false;
+ }
+ if (responseObjects.containsKey(property.toString())) {
+ elc.setPropertyResolved(true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Object getValue(ELContext elc, Object base, Object property) {
+ if (isHandled(elc, base, property)) {
+ return responseObjects.get(property.toString());
+ }
+ return null;
+ }
+
+ @Override
+ public Class<?> getType(ELContext elc, Object o, Object o1) {
+ if (isHandled(elc, o, o1)) {
+ return getValue(elc, o, o1).getClass();
+ }
+ return null;
+ }
+
+ @Override
+ public void setValue(ELContext elc, Object o, Object o1, Object o2) {
+ throw new PropertyNotWritableException(o2.toString());
+ }
+
+ @Override
+ public boolean isReadOnly(ELContext elc, Object o, Object o1) {
+ if (isHandled(elc, o, o1)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext elc, Object o) {
+ return null;
+ }
+
+ @Override
+ public Class<?> getCommonPropertyType(ELContext elc, Object o) {
+ return Object.class;
+ }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseLinkFilter.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseLinkFilter.java
new file mode 100644
index 0000000..24d83d6
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseLinkFilter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Link;
+import javax.ws.rs.core.UriInfo;
+
+import org.glassfish.jersey.linking.contributing.ResourceLinkContributionContext;
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+/**
+ * Filter that processes {@link Link} annotated fields in returned response
+ * entities.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ * @see Link
+ */
+class ResponseLinkFilter implements ContainerResponseFilter {
+
+ @Context
+ private UriInfo uriInfo;
+
+ @Context
+ private ResourceMappingContext rmc;
+
+ @Context
+ private ResourceLinkContributionContext rlcc;
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void filter(ContainerRequestContext request, ContainerResponseContext response) {
+ final Object entity = response.getEntity();
+
+ if (entity != null && !uriInfo.getMatchedResources().isEmpty()) {
+ Class<?> entityClass = entity.getClass();
+ HeaderProcessor lhp = new HeaderProcessor(entityClass);
+ lhp.processLinkHeaders(entity, uriInfo, rmc, response.getHeaders());
+ FieldProcessor lp = new FieldProcessor(entityClass);
+ lp.processLinks(entity, uriInfo, rmc, rlcc);
+ }
+
+ }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/contributing/NaiveResourceLinkContributionContext.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/contributing/NaiveResourceLinkContributionContext.java
new file mode 100644
index 0000000..ad492b1
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/contributing/NaiveResourceLinkContributionContext.java
@@ -0,0 +1,238 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.contributing;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Context;
+
+import org.glassfish.jersey.linking.ProvideLink;
+import org.glassfish.jersey.linking.ProvideLinkDescriptor;
+import org.glassfish.jersey.linking.ProvideLinks;
+import org.glassfish.jersey.server.ExtendedResourceContext;
+import org.glassfish.jersey.server.model.HandlerConstructor;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.model.MethodHandler;
+import org.glassfish.jersey.server.model.Resource;
+import org.glassfish.jersey.server.model.ResourceMethod;
+import org.glassfish.jersey.server.model.ResourceModel;
+import org.glassfish.jersey.server.model.ResourceModelComponent;
+import org.glassfish.jersey.server.model.ResourceModelVisitor;
+import org.glassfish.jersey.server.model.RuntimeResource;
+
+/**
+ * Simple map based implementation of the ResourceLinkContributionContext.
+ *
+ * @author Leonard Brünings
+ */
+public class NaiveResourceLinkContributionContext implements ResourceLinkContributionContext {
+
+ private final ExtendedResourceContext erc;
+
+ /**
+ * Mappings holds a single-level mapping between a class, and it's {@link ProvideLinkDescriptor}s
+ */
+ private Map<Class<?>, List<ProvideLinkDescriptor>> mappings;
+
+ /**
+ * Contributions holds all contributions for a class, this includes all contributions from it's ancestors.
+ */
+ private Map<Class<?>, List<ProvideLinkDescriptor>> contributions = new ConcurrentHashMap<>();
+
+ /**
+ * C'tor
+ * @param erc the ExtendedResourceContext
+ */
+ public NaiveResourceLinkContributionContext(@Context final ExtendedResourceContext erc) {
+ this.erc = erc;
+ }
+
+ @Override
+ public List<ProvideLinkDescriptor> getContributorsFor(Class<?> entityClass) {
+ buildMappings();
+ return contributions.computeIfAbsent(entityClass,
+ aClass -> Collections.unmodifiableList(collectContributors(aClass, new ArrayList<>())));
+ }
+
+ /**
+ * Collects contributors for a class recursively up the ancestors chain.
+ *
+ * @param entityClass the class to
+ * @param contributors collector list
+ * @return contributors list for easier use in lambdas
+ */
+ private List<ProvideLinkDescriptor> collectContributors(Class<?> entityClass, List<ProvideLinkDescriptor> contributors) {
+ contributors.addAll(mappings.getOrDefault(entityClass, Collections.emptyList()));
+ Class<?> sc = entityClass.getSuperclass();
+ if (sc != null && sc != Object.class) {
+ collectContributors(sc, contributors);
+ }
+ return contributors;
+ }
+
+ private void buildMappings() {
+ if (mappings != null) {
+ return;
+ }
+ final Map<Class<?>, List<ProvideLinkDescriptor>> newMappings = new HashMap<>();
+
+ erc.getResourceModel().accept(new ResourceModelVisitor() {
+
+ private void processComponents(final ResourceModelComponent component) {
+
+ final List<? extends ResourceModelComponent> components = component.getComponents();
+ if (components != null) {
+ for (final ResourceModelComponent rc : components) {
+ rc.accept(this);
+ }
+ }
+ }
+
+ @Override
+ public void visitInvocable(final Invocable invocable) {
+ processComponents(invocable);
+ }
+
+ @Override
+ public void visitRuntimeResource(final RuntimeResource runtimeResource) {
+ processComponents(runtimeResource);
+ }
+
+ @Override
+ public void visitResourceModel(final ResourceModel resourceModel) {
+ processComponents(resourceModel);
+ }
+
+ @Override
+ public void visitResourceHandlerConstructor(final HandlerConstructor handlerConstructor) {
+ processComponents(handlerConstructor);
+ }
+
+ @Override
+ public void visitMethodHandler(final MethodHandler methodHandler) {
+ processComponents(methodHandler);
+ }
+
+ @Override
+ public void visitChildResource(final Resource resource) {
+ processComponents(resource);
+ }
+
+ @Override
+ public void visitResource(final Resource resource) {
+ processComponents(resource);
+ }
+
+ @Override
+ public void visitResourceMethod(final ResourceMethod resourceMethod) {
+
+ if (resourceMethod.isExtended()) {
+ return;
+ }
+
+ if (resourceMethod.getInvocable() != null) {
+ final Invocable i = resourceMethod.getInvocable();
+
+ Method method = i.getDefinitionMethod();
+
+ List<ProvideLinkDescriptor> linkDescriptors = new ArrayList<>();
+
+ handleMetaAnnotations(resourceMethod, method, linkDescriptors);
+
+ handleAnnotations(resourceMethod, linkDescriptors, method, null);
+
+ for (ProvideLinkDescriptor linkDescriptor : linkDescriptors) {
+ for (Class<?> target : linkDescriptor.getProvideLink().value()) {
+ target = handleInheritedTarget(linkDescriptor, target);
+ newMappings.computeIfAbsent(target, aClass -> new ArrayList<>()).add(linkDescriptor);
+ }
+ }
+ }
+
+ processComponents(resourceMethod);
+ }
+
+ private Class<?> handleInheritedTarget(ProvideLinkDescriptor linkDescriptor, Class<?> target) {
+ if (Objects.equals(ProvideLink.InheritFromAnnotation.class, target)) {
+ Annotation parentAnnotation = linkDescriptor.getParentAnnotation();
+ if (parentAnnotation == null) {
+ throw new IllegalArgumentException("InheritFromAnnotation can only be used for Annotations");
+ }
+ return findTarget(parentAnnotation);
+ }
+ return target;
+ }
+
+ private Class<?> findTarget(Annotation parentAnnotation) {
+ Method[] methods = parentAnnotation.annotationType().getDeclaredMethods();
+ for (Method method : methods) {
+ if (method.isAccessible() || Class.class.isAssignableFrom(method.getReturnType())) {
+
+ try {
+ return (Class<?>) method.invoke(parentAnnotation);
+ } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException ex) {
+ Logger.getLogger(NaiveResourceLinkContributionContext.class.getName()).log(Level.FINE, null, ex);
+ }
+
+ }
+ }
+ throw new IllegalArgumentException("No suitable element of type Class<?> found on: "
+ + parentAnnotation.getClass());
+ }
+
+ private void handleMetaAnnotations(ResourceMethod resourceMethod, Method method,
+ List<ProvideLinkDescriptor> linkDescriptors) {
+ Annotation[] annotations = method.getDeclaredAnnotations();
+ for (Annotation annotation : annotations) {
+ handleAnnotations(resourceMethod, linkDescriptors, annotation.annotationType(), annotation);
+ }
+ }
+
+ private void handleAnnotations(ResourceMethod resourceMethod, List<ProvideLinkDescriptor> linkDescriptors,
+ AnnotatedElement element, Annotation parentAnnotation) {
+ if (element.isAnnotationPresent(ProvideLink.class) || element.isAnnotationPresent(ProvideLinks.class)) {
+ ProvideLink provideLink = element.getAnnotation(ProvideLink.class);
+ if (provideLink != null) {
+ linkDescriptors.add(new ProvideLinkDescriptor(resourceMethod, provideLink, parentAnnotation));
+ }
+ ProvideLinks provideLinks = element.getAnnotation(ProvideLinks.class);
+ if (provideLinks != null) {
+ for (ProvideLink link : provideLinks.value()) {
+ linkDescriptors.add(new ProvideLinkDescriptor(resourceMethod, link, parentAnnotation));
+ }
+ }
+
+ }
+ }
+
+ });
+
+ mappings = newMappings;
+ }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/contributing/ResourceLinkContributionContext.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/contributing/ResourceLinkContributionContext.java
new file mode 100644
index 0000000..9a4aaf5
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/contributing/ResourceLinkContributionContext.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.contributing;
+
+import java.util.List;
+
+import org.glassfish.jersey.linking.ProvideLinkDescriptor;
+
+/**
+ * The ResourceLinkContributionContext provides access for link contributions from other sources to an entity.
+ *
+ * @author Leonard Brünings
+ */
+public interface ResourceLinkContributionContext {
+
+ /**
+ * Returns all link contributions for an entity class.
+ *
+ * It also includes contributions for every ancestor of entityClass.
+ *
+ * @param entityClass the entityClass
+ * @return list of link contributions to add to the class
+ */
+ List<ProvideLinkDescriptor> getContributorsFor(Class<?> entityClass);
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/NaiveResourceMappingContext.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/NaiveResourceMappingContext.java
new file mode 100644
index 0000000..46f7617
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/NaiveResourceMappingContext.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.mapping;
+
+import java.lang.reflect.Type;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Context;
+
+import org.glassfish.jersey.process.Inflector;
+import org.glassfish.jersey.server.ExtendedResourceContext;
+import org.glassfish.jersey.server.model.HandlerConstructor;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.model.MethodHandler;
+import org.glassfish.jersey.server.model.Resource;
+import org.glassfish.jersey.server.model.ResourceMethod;
+import org.glassfish.jersey.server.model.ResourceModel;
+import org.glassfish.jersey.server.model.ResourceModelComponent;
+import org.glassfish.jersey.server.model.ResourceModelVisitor;
+import org.glassfish.jersey.server.model.RuntimeResource;
+import org.glassfish.jersey.uri.PathPattern;
+import org.glassfish.jersey.uri.UriTemplate;
+
+/**
+ * This implementation of the resource mapping context assumed resources are
+ * of a simple type with a statically defined structure.
+ *
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+
+public class NaiveResourceMappingContext implements ResourceMappingContext {
+
+ private final ExtendedResourceContext erc;
+
+ private Map<Class<?>, ResourceMappingContext.Mapping> mappings;
+
+ public NaiveResourceMappingContext(@Context final ExtendedResourceContext erc) {
+ this.erc = erc;
+ }
+
+ @Override
+ public Mapping getMapping(final Class<?> resource) {
+ buildMappings();
+ return mappings.get(resource);
+ }
+
+ private void buildMappings() {
+ if (mappings != null) {
+ return;
+ }
+ mappings = new HashMap<>();
+
+ erc.getResourceModel().accept(new ResourceModelVisitor() {
+
+ Deque<PathPattern> stack = new LinkedList<>();
+
+ private void processComponents(final ResourceModelComponent component) {
+
+ final List<? extends ResourceModelComponent> components = component.getComponents();
+ if (components != null) {
+ for (final ResourceModelComponent rc : components) {
+ rc.accept(this);
+ }
+ }
+ }
+
+ @Override
+ public void visitInvocable(final Invocable invocable) {
+ processComponents(invocable);
+ }
+
+ @Override
+ public void visitRuntimeResource(final RuntimeResource runtimeResource) {
+ processComponents(runtimeResource);
+ }
+
+ @Override
+ public void visitResourceModel(final ResourceModel resourceModel) {
+ processComponents(resourceModel);
+ }
+
+ @Override
+ public void visitResourceHandlerConstructor(final HandlerConstructor handlerConstructor) {
+ processComponents(handlerConstructor);
+ }
+
+ @Override
+ public void visitMethodHandler(final MethodHandler methodHandler) {
+ processComponents(methodHandler);
+ }
+
+ @Override
+ public void visitChildResource(final Resource resource) {
+ visitResourceIntl(resource, false);
+ }
+
+ @Override
+ public void visitResource(final Resource resource) {
+
+ visitResourceIntl(resource, true);
+ }
+
+ private void visitResourceIntl(final Resource resource, final boolean isRoot) {
+ try {
+ stack.addLast(resource.getPathPattern());
+ processComponents(resource);
+
+ if (isRoot) {
+ Class likelyToBeRoot = null;
+ for (final Class next : resource.getHandlerClasses()) {
+ if (!(Inflector.class.isAssignableFrom(next))) {
+ likelyToBeRoot = next;
+ }
+ }
+
+ if (likelyToBeRoot != null) {
+ mappings.put(likelyToBeRoot, getMapping(getTemplate()));
+ }
+ }
+ } finally {
+ stack.removeLast();
+ }
+ }
+
+ @Override
+ public void visitResourceMethod(final ResourceMethod resourceMethod) {
+
+ if (resourceMethod.isExtended()) {
+ return;
+ }
+
+ if (ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR.equals(resourceMethod.getType())) {
+ if (resourceMethod.getInvocable() != null) {
+ final Invocable i = resourceMethod.getInvocable();
+
+ final Type type = i.getResponseType();
+ final StringBuilder template = getTemplate();
+
+ mappings.put((Class) type, getMapping(template));
+
+ // Process sub resources ?
+
+ Resource.Builder builder = Resource
+ .builder(i.getRawResponseType());
+ if (builder == null) {
+ // for example in the case the return type of the sub resource locator is Object
+ builder = Resource.builder().path(resourceMethod.getParent().getPath());
+ }
+ final Resource subResource = builder.build();
+
+ visitChildResource(subResource);
+ }
+ }
+
+ processComponents(resourceMethod);
+ }
+
+ private StringBuilder getTemplate() {
+ final StringBuilder template = new StringBuilder();
+ for (final PathPattern pp : stack) {
+ final String ppTemplate = pp.getTemplate().getTemplate();
+
+ final int tlength = template.length();
+ if (tlength > 0) {
+ if (template.charAt(tlength - 1) == '/') {
+ if (ppTemplate.startsWith("/")) {
+ template.append(ppTemplate, 1, ppTemplate.length());
+ } else {
+ template.append(ppTemplate);
+ }
+ } else {
+ if (ppTemplate.startsWith("/")) {
+ template.append(ppTemplate);
+ } else {
+ template.append("/");
+ template.append(ppTemplate);
+ }
+ }
+ } else {
+ template.append(ppTemplate);
+ }
+
+ }
+ return template;
+ }
+ });
+
+ }
+
+ private Mapping getMapping(final StringBuilder template) {
+ return new Mapping() {
+ UriTemplate uriTemplate = new UriTemplate(template.toString());
+
+ @Override
+ public UriTemplate getTemplate() {
+ return uriTemplate;
+ }
+ };
+ }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/ResourceMappingContext.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/ResourceMappingContext.java
new file mode 100644
index 0000000..9d04d89
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/ResourceMappingContext.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.mapping;
+
+import org.glassfish.jersey.uri.UriTemplate;
+
+/**
+ * This service tries to work out the UriTemplate required for a particular
+ * resource class.
+ *
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+
+public interface ResourceMappingContext {
+
+ public interface Mapping {
+
+ public UriTemplate getTemplate();
+ }
+
+ public Mapping getMapping(Class<?> resource);
+}
diff --git a/incubator/declarative-linking/src/main/resources/org/glassfish/jersey/media/linking/internal/linkMessages.properties b/incubator/declarative-linking/src/main/resources/org/glassfish/jersey/media/linking/internal/linkMessages.properties
new file mode 100644
index 0000000..681e193
--- /dev/null
+++ b/incubator/declarative-linking/src/main/resources/org/glassfish/jersey/media/linking/internal/linkMessages.properties
@@ -0,0 +1,18 @@
+#
+# 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 Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+warning.linkfilter.processing=LinkFilter cannot process class {0}, exception occurred during processing. Class will be ignored \
+ in the LinkFilter.
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/EntityDescriptorTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/EntityDescriptorTest.java
new file mode 100644
index 0000000..5ed63fd
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/EntityDescriptorTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.net.URI;
+import java.util.Iterator;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+public class EntityDescriptorTest {
+
+ public static class TestClassA {
+
+ @InjectLink
+ protected String foo;
+
+ @InjectLink
+ private String bar;
+
+ public String baz;
+ }
+
+ ResourceMappingContext mockRmc = new ResourceMappingContext() {
+
+ @Override
+ public ResourceMappingContext.Mapping getMapping(Class<?> resource) {
+ return null;
+ }
+ };
+
+ /**
+ * Test for declared properties
+ */
+ @Test
+ public void testDeclaredProperties() {
+ System.out.println("Declared properties");
+ EntityDescriptor instance = EntityDescriptor.getInstance(TestClassA.class);
+ assertEquals(2, instance.getLinkFields().size());
+ assertEquals(1, instance.getNonLinkFields().size());
+ }
+
+ public static class TestClassB extends TestClassA {
+
+ @InjectLink
+ private String bar;
+ }
+
+ /**
+ * Test for inherited properties
+ */
+ @Test
+ public void testInheritedProperties() {
+ System.out.println("Inherited properties");
+ EntityDescriptor instance = EntityDescriptor.getInstance(TestClassB.class);
+ assertEquals(2, instance.getLinkFields().size());
+ assertEquals(1, instance.getNonLinkFields().size());
+ }
+
+ private static final String TEMPLATE_A = "foo";
+
+ @Path(TEMPLATE_A)
+ public static class TestResourceA {
+ }
+
+ public static class TestClassC {
+
+ @InjectLink(resource = TestResourceA.class, bindings = {@Binding(name = "bar", value = "baz")})
+ String res;
+ }
+
+ @Test
+ public void testResourceLink() {
+ System.out.println("Resource class link");
+ EntityDescriptor instance = EntityDescriptor.getInstance(TestClassC.class);
+ assertEquals(1, instance.getLinkFields().size());
+ assertEquals(0, instance.getNonLinkFields().size());
+ InjectLinkFieldDescriptor linkDesc = (InjectLinkFieldDescriptor) instance.getLinkFields().iterator().next();
+ assertEquals(TEMPLATE_A, linkDesc.getLinkTemplate(mockRmc));
+ assertEquals("baz", linkDesc.getBinding("bar"));
+ }
+
+ public static class TestClassD {
+
+ @InjectLink(value = TEMPLATE_A, style = InjectLink.Style.RELATIVE_PATH)
+ private String res1;
+
+ @InjectLink(value = TEMPLATE_A, style = InjectLink.Style.RELATIVE_PATH)
+ private URI res2;
+ }
+
+ @Test
+ public void testStringLink() {
+ System.out.println("String link");
+ EntityDescriptor instance = EntityDescriptor.getInstance(TestClassD.class);
+ assertEquals(2, instance.getLinkFields().size());
+ assertEquals(0, instance.getNonLinkFields().size());
+ Iterator<FieldDescriptor> i = instance.getLinkFields().iterator();
+ while (i.hasNext()) {
+ InjectLinkFieldDescriptor linkDesc = (InjectLinkFieldDescriptor) i.next();
+ assertEquals(TEMPLATE_A, linkDesc.getLinkTemplate(mockRmc));
+ }
+ }
+
+ @Test
+ public void testSetLink() {
+ System.out.println("Set link");
+ EntityDescriptor instance = EntityDescriptor.getInstance(TestClassD.class);
+ Iterator<FieldDescriptor> i = instance.getLinkFields().iterator();
+ TestClassD testClass = new TestClassD();
+ while (i.hasNext()) {
+ InjectLinkFieldDescriptor linkDesc = (InjectLinkFieldDescriptor) i.next();
+ URI value = UriBuilder.fromPath(linkDesc.getLinkTemplate(mockRmc)).build();
+ linkDesc.setPropertyValue(testClass, value);
+ }
+ assertEquals(TEMPLATE_A, testClass.res1);
+ assertEquals(TEMPLATE_A, testClass.res2.toString());
+ }
+
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java
new file mode 100644
index 0000000..e0afeba
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java
@@ -0,0 +1,853 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Filter;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.regex.MatchResult;
+import java.util.zip.ZipEntry;
+import javax.ws.rs.BeanParam;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Link;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.UriBuilder;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;
+import org.glassfish.jersey.linking.contributing.ResourceLinkContributionContext;
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.model.Resource;
+import org.glassfish.jersey.server.model.ResourceMethod;
+import org.glassfish.jersey.server.model.RuntimeResource;
+import org.glassfish.jersey.uri.UriTemplate;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+public class FieldProcessorTest {
+
+ private static final Logger LOG = Logger.getLogger(FieldProcessor.class.getName());
+
+ ExtendedUriInfo mockUriInfo = new ExtendedUriInfo() {
+
+ private static final String baseURI = "http://example.com/application/resources";
+
+ private MultivaluedMap queryParams = new MultivaluedStringMap();
+
+ @Override
+ public String getPath() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public String getPath(boolean decode) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public List<PathSegment> getPathSegments() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public List<PathSegment> getPathSegments(boolean decode) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public URI getRequestUri() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public UriBuilder getRequestUriBuilder() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public URI getAbsolutePath() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public UriBuilder getAbsolutePathBuilder() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public URI getBaseUri() {
+ return getBaseUriBuilder().build();
+ }
+
+ @Override
+ public UriBuilder getBaseUriBuilder() {
+ return UriBuilder.fromUri(baseURI);
+ }
+
+ @Override
+ public MultivaluedMap<String, String> getPathParameters() {
+ return new MultivaluedStringMap();
+ }
+
+ @Override
+ public MultivaluedMap<String, String> getPathParameters(boolean decode) {
+ return new MultivaluedStringMap();
+ }
+
+ @Override
+ public MultivaluedMap<String, String> getQueryParameters() {
+ return queryParams;
+ }
+
+ @Override
+ public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
+ return queryParams;
+ }
+
+ @Override
+ public List<String> getMatchedURIs() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public List<String> getMatchedURIs(boolean decode) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public List<Object> getMatchedResources() {
+ Object dummyResource = new Object() {
+ };
+ return Collections.singletonList(dummyResource);
+ }
+
+ @Override
+ public Throwable getMappedThrowable() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public List<MatchResult> getMatchedResults() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public List<UriTemplate> getMatchedTemplates() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public List<PathSegment> getPathSegments(String name) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public List<PathSegment> getPathSegments(String name, boolean decode) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public List<RuntimeResource> getMatchedRuntimeResources() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public ResourceMethod getMatchedResourceMethod() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public Resource getMatchedModelResource() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public List<ResourceMethod> getMatchedResourceLocators() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public List<Resource> getLocatorSubResources() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public URI resolve(URI uri) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public URI relativize(URI uri) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ };
+
+ private final ResourceMappingContext mockRmc = new ResourceMappingContext() {
+
+ @Override
+ public ResourceMappingContext.Mapping getMapping(Class<?> resource) {
+ return null;
+ }
+ };
+
+ protected final ResourceLinkContributionContext mockRlcc = new ResourceLinkContributionContext() {
+ @Override
+ public List<ProvideLinkDescriptor> getContributorsFor(Class<?> entityClass) {
+ return Collections.emptyList();
+ }
+ };
+
+ private static final String TEMPLATE_A = "foo";
+
+ public static class TestClassD {
+
+ @InjectLink(value = TEMPLATE_A, style = InjectLink.Style.RELATIVE_PATH)
+ private String res1;
+
+ @InjectLink(value = TEMPLATE_A, style = InjectLink.Style.RELATIVE_PATH)
+ private URI res2;
+ }
+
+ @Test
+ public void testProcessLinks() {
+ LOG.info("Links");
+
+ FieldProcessor<TestClassD> instance = new FieldProcessor(TestClassD.class);
+ TestClassD testClass = new TestClassD();
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals(TEMPLATE_A, testClass.res1);
+ assertEquals(TEMPLATE_A, testClass.res2.toString());
+ }
+
+ private static final String TEMPLATE_B = "widgets/{id}";
+
+ public static class TestClassE {
+
+ @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH)
+ private String link;
+
+ private String id;
+
+ public TestClassE(String id) {
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+ }
+
+ @Test
+ public void testProcessLinksWithFields() {
+ LOG.info("Links from field values");
+ FieldProcessor<TestClassE> instance = new FieldProcessor(TestClassE.class);
+ TestClassE testClass = new TestClassE("10");
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("widgets/10", testClass.link);
+ }
+
+ public static class TestClassF {
+
+ @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH)
+ private String thelink;
+
+ private String id;
+ private TestClassE nested;
+
+ public TestClassF(String id, TestClassE e) {
+ this.id = id;
+ this.nested = e;
+ }
+
+ public String getId() {
+ return id;
+ }
+ }
+
+ @Test
+ public void testNesting() {
+ LOG.info("Nesting");
+ FieldProcessor<TestClassF> instance = new FieldProcessor(TestClassF.class);
+ TestClassE nested = new TestClassE("10");
+ TestClassF testClass = new TestClassF("20", nested);
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("widgets/20", testClass.thelink);
+ assertEquals("widgets/10", testClass.nested.link);
+ }
+
+ @Test
+ public void testArray() {
+ LOG.info("Array");
+ FieldProcessor<TestClassE[]> instance = new FieldProcessor(TestClassE[].class);
+ TestClassE item1 = new TestClassE("10");
+ TestClassE item2 = new TestClassE("20");
+ TestClassE array[] = {item1, item2};
+ instance.processLinks(array, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("widgets/10", array[0].link);
+ assertEquals("widgets/20", array[1].link);
+ }
+
+ @Test
+ public void testCollection() {
+ LOG.info("Collection");
+ FieldProcessor<List> instance = new FieldProcessor(List.class);
+ TestClassE item1 = new TestClassE("10");
+ TestClassE item2 = new TestClassE("20");
+ List<TestClassE> list = Arrays.asList(item1, item2);
+ instance.processLinks(list, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("widgets/10", list.get(0).link);
+ assertEquals("widgets/20", list.get(1).link);
+ }
+
+ @Test
+ public void testMap() {
+ LOG.info("Map");
+ FieldProcessor<Map> instance = new FieldProcessor(Map.class);
+ TestClassE item1 = new TestClassE("10");
+ TestClassE item2 = new TestClassE("20");
+ Map<String, TestClassE> map = new HashMap<>();
+ for (TestClassE item : Arrays.asList(item1, item2)) {
+ map.put(item.getId(), item);
+ }
+ instance.processLinks(map, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("widgets/10", map.get("10").link);
+ assertEquals("widgets/20", map.get("20").link);
+ }
+
+ public static class TestClassG {
+
+ @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH)
+ private String relativePath;
+
+ @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.ABSOLUTE_PATH)
+ private String absolutePath;
+
+ @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.ABSOLUTE)
+ private String absolute;
+
+ @InjectLink(TEMPLATE_B)
+ private String defaultStyle;
+
+ private String id;
+
+ public TestClassG(String id) {
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+ }
+
+ @Test
+ public void testLinkStyles() {
+ LOG.info("Link styles");
+ FieldProcessor<TestClassG> instance = new FieldProcessor(TestClassG.class);
+ TestClassG testClass = new TestClassG("10");
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("widgets/10", testClass.relativePath);
+ assertEquals("/application/resources/widgets/10", testClass.absolutePath);
+ assertEquals("/application/resources/widgets/10", testClass.defaultStyle);
+ assertEquals("http://example.com/application/resources/widgets/10", testClass.absolute);
+ }
+
+ public static class TestClassH {
+
+ @InjectLink(TEMPLATE_B)
+ private String link;
+
+ public String getId() {
+ return "10";
+ }
+ }
+
+ @Test
+ public void testComputedProperty() {
+ LOG.info("Computed property");
+ FieldProcessor<TestClassH> instance = new FieldProcessor(TestClassH.class);
+ TestClassH testClass = new TestClassH();
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("/application/resources/widgets/10", testClass.link);
+ }
+
+ public static class TestClassI {
+
+ @InjectLink("widgets/${entity.id}")
+ private String link;
+
+ public String getId() {
+ return "10";
+ }
+ }
+
+ @Test
+ public void testEL() {
+ LOG.info("El link");
+ FieldProcessor<TestClassI> instance = new FieldProcessor(TestClassI.class);
+ TestClassI testClass = new TestClassI();
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("/application/resources/widgets/10", testClass.link);
+ }
+
+ public static class TestClassJ {
+
+ @InjectLink("widgets/${entity.id}/widget/{id}")
+ private String link;
+
+ public String getId() {
+ return "10";
+ }
+ }
+
+ @Test
+ public void testMixed() {
+ LOG.info("Mixed EL and template vars link");
+ FieldProcessor<TestClassJ> instance = new FieldProcessor(TestClassJ.class);
+ TestClassJ testClass = new TestClassJ();
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("/application/resources/widgets/10/widget/10", testClass.link);
+ }
+
+ public static class DependentInnerBean {
+
+ @InjectLink("${entity.id}")
+ public String outerUri;
+ @InjectLink("${instance.id}")
+ public String innerUri;
+
+ public String getId() {
+ return "inner";
+ }
+ }
+
+ public static class OuterBean {
+
+ public DependentInnerBean inner = new DependentInnerBean();
+
+ public String getId() {
+ return "outer";
+ }
+ }
+
+ @Test
+ public void testELScopes() {
+ LOG.info("EL scopes");
+ FieldProcessor<OuterBean> instance = new FieldProcessor(OuterBean.class);
+ OuterBean testClass = new OuterBean();
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("/application/resources/inner", testClass.inner.innerUri);
+ assertEquals("/application/resources/outer", testClass.inner.outerUri);
+ }
+
+ public static class BoundLinkBean {
+
+ @InjectLink(value = "{id}", bindings = {@Binding(name = "id", value = "${instance.name}")})
+ public String uri;
+
+ public String getName() {
+ return "name";
+ }
+ }
+
+ @Test
+ public void testELBinding() {
+ LOG.info("EL binding");
+ FieldProcessor<BoundLinkBean> instance = new FieldProcessor(BoundLinkBean.class);
+ BoundLinkBean testClass = new BoundLinkBean();
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("/application/resources/name", testClass.uri);
+ }
+
+ public static class BoundLinkOnLinkBean {
+
+ @InjectLink(value = "{id}",
+ bindings = {@Binding(name = "id", value = "${instance.name}")},
+ rel = "self")
+ public Link link;
+
+ public String getName() {
+ return "name";
+ }
+ }
+
+ @Test
+ public void testELBindingOnLink() {
+ LOG.info("EL binding");
+ FieldProcessor<BoundLinkOnLinkBean> instance = new FieldProcessor(BoundLinkOnLinkBean.class);
+ BoundLinkOnLinkBean testClass = new BoundLinkOnLinkBean();
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("/application/resources/name", testClass.link.getUri().toString());
+ assertEquals("self", testClass.link.getRel());
+ }
+
+ public static class BoundLinkOnLinksBean {
+
+ @InjectLinks({
+ @InjectLink(value = "{id}",
+ bindings = {@Binding(name = "id", value = "${instance.name}")},
+ rel = "self"),
+ @InjectLink(value = "{id}",
+ bindings = {@Binding(name = "id", value = "${instance.name}")},
+ rel = "other"),
+
+ })
+ public List<Link> links;
+
+ @InjectLinks({
+ @InjectLink(value = "{id}",
+ bindings = {@Binding(name = "id", value = "${instance.name}")},
+ rel = "self"),
+ @InjectLink(value = "{id}",
+ bindings = {@Binding(name = "id", value = "${instance.name}")},
+ rel = "other"),
+
+ })
+ public Link[] linksArray;
+
+ public String getName() {
+ return "name";
+ }
+ }
+
+ @Test
+ public void testELBindingOnLinks() {
+ LOG.info("EL binding");
+ FieldProcessor<BoundLinkOnLinksBean> instance = new FieldProcessor(BoundLinkOnLinksBean.class);
+ BoundLinkOnLinksBean testClass = new BoundLinkOnLinksBean();
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("/application/resources/name", testClass.links.get(0).getUri().toString());
+ assertEquals("self", testClass.links.get(0).getRel());
+ assertEquals("other", testClass.links.get(1).getRel());
+
+ assertEquals("/application/resources/name", testClass.linksArray[0].getUri().toString());
+ assertEquals("self", testClass.linksArray[0].getRel());
+ assertEquals("other", testClass.linksArray[1].getRel());
+
+ }
+
+ public static class ConditionalLinkBean {
+
+ @InjectLink(value = "{id}", condition = "${entity.uri1Enabled}")
+ public String uri1;
+
+ @InjectLink(value = "{id}", condition = "${entity.uri2Enabled}")
+ public String uri2;
+
+ public String getId() {
+ return "name";
+ }
+
+ public boolean isUri1Enabled() {
+ return true;
+ }
+
+ public boolean isUri2Enabled() {
+ return false;
+ }
+ }
+
+ @Test
+ public void testCondition() {
+ LOG.info("Condition");
+ FieldProcessor<ConditionalLinkBean> instance = new FieldProcessor(ConditionalLinkBean.class);
+ ConditionalLinkBean testClass = new ConditionalLinkBean();
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("/application/resources/name", testClass.uri1);
+ assertEquals(null, testClass.uri2);
+ }
+
+ @Path("a")
+ public static class SubResource {
+
+ @Path("b")
+ @GET
+ public String getB() {
+ return "hello world";
+ }
+ }
+
+ public static class SubResourceBean {
+
+ @InjectLink(resource = SubResource.class, method = "getB")
+ public String uri;
+ }
+
+ @Test
+ public void testSubresource() {
+ LOG.info("Subresource");
+ FieldProcessor<SubResourceBean> instance = new FieldProcessor(SubResourceBean.class);
+ SubResourceBean testClass = new SubResourceBean();
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("/application/resources/a/b", testClass.uri);
+ }
+
+ @Path("a")
+ public static class QueryResource {
+
+ @Path("b")
+ @GET
+ public String getB(@QueryParam("query") String query, @QueryParam("query2") String query2) {
+ return "hello world";
+ }
+ }
+
+ public static class QueryResourceBean {
+
+ public String getQueryParam() {
+ return queryExample;
+ }
+
+ private String queryExample;
+
+ public QueryResourceBean(String queryExample, String queryExample2) {
+ this.queryExample = queryExample;
+ this.queryExample2 = queryExample2;
+ }
+
+ public String getQueryParam2() {
+ return queryExample2;
+ }
+
+ private String queryExample2;
+
+ @InjectLink(resource = QueryResource.class, method = "getB",
+ bindings = {
+ @Binding(name = "query", value = "${instance.queryParam}"),
+ @Binding(name = "query2", value = "${instance.queryParam2}")
+ })
+ public String uri;
+ }
+
+ public static class QueryResourceBeanNoBindings {
+ //query parameters will be populated from uriInfo
+ //JERSEY-2863
+ @InjectLink(resource = QueryResource.class, method = "getB")
+ public String uri;
+ }
+
+ @Test
+ public void testQueryResource() {
+ LOG.info("QueryResource");
+ FieldProcessor<QueryResourceBean> instance = new FieldProcessor(QueryResourceBean.class);
+ QueryResourceBean testClass = new QueryResourceBean("queryExample", null);
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("/application/resources/a/b?query=queryExample&query2=", testClass.uri);
+ }
+
+ @Test
+ public void testDoubleQueryResource() {
+ LOG.info("QueryResource");
+ FieldProcessor<QueryResourceBean> instance = new FieldProcessor(QueryResourceBean.class);
+ QueryResourceBean testClass = new QueryResourceBean("queryExample", "queryExample2");
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("/application/resources/a/b?query=queryExample&query2=queryExample2", testClass.uri);
+ }
+
+ @Test
+ public void testQueryResourceWithoutBindings() {
+ LOG.info("QueryResource");
+ FieldProcessor<QueryResourceBeanNoBindings> instance = new FieldProcessor(QueryResourceBeanNoBindings.class);
+ QueryResourceBeanNoBindings testClass = new QueryResourceBeanNoBindings();
+ mockUriInfo.getQueryParameters().putSingle("query", "queryExample");
+ mockUriInfo.getQueryParameters().putSingle("query2", "queryExample2");
+ assertEquals("queryExample", mockUriInfo.getQueryParameters().getFirst("query"));
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("/application/resources/a/b?query=queryExample&query2=queryExample2", testClass.uri);
+ //clean mock
+ mockUriInfo.getQueryParameters().clear();
+ }
+
+ /** Bean param with method setter QueryParam. */
+ public static class BeanParamBeanA {
+ private String qparam;
+ @QueryParam("qparam")
+ public void setQParam(String qparam) {
+ this.qparam = qparam;
+ }
+ }
+
+ /** Bean param with field QueryParam. */
+ public static class BeanParamBeanB {
+ @QueryParam("query") public String query;
+ }
+
+ @Path("a")
+ public static class BeanParamQueryResource {
+
+ @Path("b")
+ @GET
+ public String getB(@BeanParam BeanParamBeanA beanParamBeanA, @BeanParam BeanParamBeanB beanParamBeanB) {
+ return "hello world";
+ }
+ }
+
+ public static class BeanParamResourceBean {
+
+ public String getQueryParam() {
+ return queryExample;
+ }
+
+ private String queryExample;
+
+ public BeanParamResourceBean(String queryExample) {
+ this.queryExample = queryExample;
+ }
+
+ @InjectLink(resource = BeanParamQueryResource.class, method = "getB",
+ bindings = {
+ @Binding(name = "query", value = "${instance.queryParam}"),
+ @Binding(name = "qparam", value = "foo")
+ })
+ public String uri;
+ }
+
+ @Test
+ public void testBeanParamResource() {
+ LOG.info("BeanParamResource");
+ FieldProcessor<BeanParamResourceBean> instance = new FieldProcessor(BeanParamResourceBean.class);
+ BeanParamResourceBean testClass = new BeanParamResourceBean("queryExample");
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("/application/resources/a/b?qparam=foo&query=queryExample", testClass.uri);
+ }
+
+ public static class TestClassK {
+
+ public static final ZipEntry zipEntry = new ZipEntry("entry");
+ }
+
+ public static class TestClassL {
+
+ public final ZipEntry zipEntry = new ZipEntry("entry");
+ }
+
+ private class LoggingFilter implements Filter {
+
+ private int count = 0;
+
+ @Override
+ public synchronized boolean isLoggable(LogRecord logRecord) {
+ if (logRecord.getThrown() instanceof IllegalAccessException) {
+ count++;
+ return false;
+ }
+ return true;
+ }
+
+ public int getCount() {
+ return count;
+ }
+ }
+
+ @Test
+ public void testKL() {
+ final LoggingFilter lf = new LoggingFilter();
+
+ Logger.getLogger(FieldDescriptor.class.getName()).setFilter(lf);
+ assertTrue(lf.getCount() == 0);
+
+ FieldProcessor<TestClassK> instanceK = new FieldProcessor(TestClassK.class);
+ TestClassK testClassK = new TestClassK();
+ instanceK.processLinks(testClassK, mockUriInfo, mockRmc, mockRlcc);
+
+ assertTrue(lf.getCount() == 0);
+
+ FieldProcessor<TestClassL> instanceL = new FieldProcessor(TestClassL.class);
+ TestClassL testClassL = new TestClassL();
+ instanceL.processLinks(testClassL, mockUriInfo, mockRmc, mockRlcc);
+
+ assertTrue(lf.getCount() == 0);
+
+ Logger.getLogger(FieldDescriptor.class.getName()).setFilter(null);
+
+ }
+
+ public static class TestClassM {
+
+ @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH)
+ private String thelink;
+
+ private String id;
+
+ @InjectLinkNoFollow
+ private TestClassE nested;
+
+ @XmlTransient
+ private TestClassE transientNested;
+
+ public TestClassM(String id, TestClassE e, TestClassE transientNested) {
+ this.id = id;
+ this.nested = e;
+ this.transientNested = transientNested;
+ }
+
+ public String getId() {
+ return id;
+ }
+ }
+
+ @Test
+ public void testNoRecursiveNesting() {
+ LOG.info("No Recursive Nesting");
+ FieldProcessor<TestClassM> instance = new FieldProcessor(TestClassM.class);
+ TestClassE nested = new TestClassE("10");
+ TestClassE transientNested = new TestClassE("30");
+ TestClassM testClass = new TestClassM("20", nested, transientNested);
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ assertEquals("widgets/20", testClass.thelink);
+ assertEquals(null, testClass.nested.link);
+ assertEquals(null, testClass.transientNested.link);
+ }
+
+ public static class TestClassN {
+ // Simulate object injected by JPA
+ // in order to test a fix for JERSEY-2625
+ private transient Iterable res1 = new Iterable() {
+ @Override
+ public Iterator iterator() {
+ throw new RuntimeException("Declarative linking feature is incorrectly processing a transient iterator");
+ }
+
+ };
+ }
+
+ @Test
+ public void testIgnoreTransient() {
+ TestClassN testClass = new TestClassN();
+ FieldProcessor<TestClassN> instance = new FieldProcessor(TestClassN.class);
+ instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+ }
+
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java
new file mode 100644
index 0000000..cf360ab
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.MatchResult;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;
+import org.glassfish.jersey.linking.InjectLink.Extension;
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.model.Resource;
+import org.glassfish.jersey.server.model.ResourceMethod;
+import org.glassfish.jersey.server.model.RuntimeResource;
+import org.glassfish.jersey.uri.UriTemplate;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+public class HeaderProcessorTest {
+
+ ExtendedUriInfo mockUriInfo = new ExtendedUriInfo() {
+
+ private static final String baseURI = "http://example.com/application/resources";
+
+ public String getPath() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public String getPath(boolean decode) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public List<PathSegment> getPathSegments() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public List<PathSegment> getPathSegments(boolean decode) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public URI getRequestUri() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public UriBuilder getRequestUriBuilder() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public URI getAbsolutePath() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public UriBuilder getAbsolutePathBuilder() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public URI getBaseUri() {
+ return getBaseUriBuilder().build();
+ }
+
+ public UriBuilder getBaseUriBuilder() {
+ return UriBuilder.fromUri(baseURI);
+ }
+
+ public MultivaluedMap<String, String> getPathParameters() {
+ return new MultivaluedStringMap();
+ }
+
+ public MultivaluedMap<String, String> getPathParameters(boolean decode) {
+ return new MultivaluedStringMap();
+ }
+
+ public MultivaluedMap<String, String> getQueryParameters() {
+ return new MultivaluedStringMap();
+ }
+
+ public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
+ return new MultivaluedStringMap();
+ }
+
+ public List<String> getMatchedURIs() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public List<String> getMatchedURIs(boolean decode) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public List<Object> getMatchedResources() {
+ Object dummyResource = new Object() {};
+ return Collections.singletonList(dummyResource);
+ }
+
+ @Override
+ public URI resolve(URI uri) {
+ throw new UnsupportedOperationException(
+ "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public URI relativize(URI uri) {
+ throw new UnsupportedOperationException(
+ "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public Throwable getMappedThrowable() {
+ throw new UnsupportedOperationException(
+ "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public List<MatchResult> getMatchedResults() {
+ throw new UnsupportedOperationException(
+ "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public List<UriTemplate> getMatchedTemplates() {
+ throw new UnsupportedOperationException(
+ "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public List<PathSegment> getPathSegments(String name) {
+ throw new UnsupportedOperationException(
+ "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public List<PathSegment> getPathSegments(String name, boolean decode) {
+ throw new UnsupportedOperationException(
+ "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public List<RuntimeResource> getMatchedRuntimeResources() {
+ throw new UnsupportedOperationException(
+ "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public ResourceMethod getMatchedResourceMethod() {
+ throw new UnsupportedOperationException(
+ "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public Resource getMatchedModelResource() {
+ throw new UnsupportedOperationException(
+ "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public List<ResourceMethod> getMatchedResourceLocators() {
+ throw new UnsupportedOperationException(
+ "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public List<Resource> getLocatorSubResources() {
+ throw new UnsupportedOperationException(
+ "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ };
+
+ ResourceMappingContext mockRmc = new ResourceMappingContext() {
+
+ @Override
+ public ResourceMappingContext.Mapping getMapping(Class<?> resource) {
+ return null;
+ }
+ };
+
+ @InjectLink(value = "A")
+ public static class EntityA {
+ }
+
+ @Test
+ public void testLiteral() {
+ System.out.println("Literal");
+ HeaderProcessor<EntityA> instance = new HeaderProcessor(EntityA.class);
+ EntityA testClass = new EntityA();
+ List<String> headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc);
+ assertEquals(1, headerValues.size());
+ String headerValue = headerValues.get(0);
+ assertEquals("</application/resources/A>", headerValue);
+ }
+
+ @InjectLink(value = "${entity.id}")
+ public static class EntityB {
+
+ public String getId() {
+ return "B";
+ }
+ }
+
+ @Test
+ public void testEL() {
+ System.out.println("EL");
+ HeaderProcessor<EntityB> instance = new HeaderProcessor(EntityB.class);
+ EntityB testClass = new EntityB();
+ List<String> headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc);
+ assertEquals(1, headerValues.size());
+ String headerValue = headerValues.get(0);
+ assertEquals("</application/resources/B>", headerValue);
+ }
+
+ @InjectLink(value = "{id}")
+ public static class EntityC {
+
+ public String getId() {
+ return "C";
+ }
+ }
+
+ @Test
+ public void testTemplateLiteral() {
+ System.out.println("Template Literal");
+ HeaderProcessor<EntityC> instance = new HeaderProcessor(EntityC.class);
+ EntityC testClass = new EntityC();
+ List<String> headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc);
+ assertEquals(1, headerValues.size());
+ String headerValue = headerValues.get(0);
+ assertEquals("</application/resources/C>", headerValue);
+ }
+
+ @InjectLinks({
+ @InjectLink(value = "A"),
+ @InjectLink(value = "B")
+ })
+ public static class EntityD {
+ }
+
+ @Test
+ public void testMultiple() {
+ System.out.println("Multiple Literal");
+ HeaderProcessor<EntityD> instance = new HeaderProcessor(EntityD.class);
+ EntityD testClass = new EntityD();
+ List<String> headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc);
+ assertEquals(2, headerValues.size());
+ // not sure if annotation order is supposed to be preserved but it seems
+ // to work as expected
+ String headerValue = headerValues.get(0);
+ assertEquals("</application/resources/A>", headerValue);
+ headerValue = headerValues.get(1);
+ assertEquals("</application/resources/B>", headerValue);
+ }
+
+ @InjectLink(value = "E",
+ rel = "relE",
+ rev = "revE",
+ type = "type/e",
+ title = "titleE",
+ anchor = "anchorE",
+ media = "mediaE",
+ hreflang = "en-E",
+ extensions = {
+ @Extension(name = "e1", value = "v1"),
+ @Extension(name = "e2", value = "v2")
+ }
+ )
+ public static class EntityE {
+ }
+
+ @Test
+ public void testParameters() {
+ System.out.println("Parameters");
+ HeaderProcessor<EntityE> instance = new HeaderProcessor(EntityE.class);
+ EntityE testClass = new EntityE();
+ List<String> headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc);
+ assertEquals(1, headerValues.size());
+ String headerValue = headerValues.get(0);
+ assertTrue(headerValue.contains("</application/resources/E>"));
+ assertTrue(headerValue.contains("; rel=\"relE\""));
+ assertTrue(headerValue.contains("; rev=\"revE\""));
+ assertTrue(headerValue.contains("; type=\"type/e\""));
+ assertTrue(headerValue.contains("; title=\"titleE\""));
+ assertTrue(headerValue.contains("; anchor=\"anchorE\""));
+ assertTrue(headerValue.contains("; media=\"mediaE\""));
+ assertTrue(headerValue.contains("; hreflang=\"en-E\""));
+ assertTrue(headerValue.contains("; e1=\"v1\""));
+ assertTrue(headerValue.contains("; e2=\"v2\""));
+ }
+
+ @InjectLinks({
+ @InjectLink(value = "${entity.id1}", condition = "${entity.id1Enabled}"),
+ @InjectLink(value = "${entity.id2}", condition = "${entity.id2Enabled}")
+ })
+ public static class EntityF {
+
+ public boolean isId1Enabled() {
+ return true;
+ }
+
+ public String getId1() {
+ return "1";
+ }
+
+ public boolean isId2Enabled() {
+ return false;
+ }
+
+ public String getId2() {
+ return "2";
+ }
+ }
+
+ @Test
+ public void testConditional() {
+ System.out.println("EL");
+ HeaderProcessor<EntityF> instance = new HeaderProcessor(EntityF.class);
+ EntityF testClass = new EntityF();
+ List<String> headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc);
+ assertEquals(1, headerValues.size());
+ String headerValue = headerValues.get(0);
+ assertEquals("</application/resources/1>", headerValue);
+ }
+
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/LinkELContextTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/LinkELContextTest.java
new file mode 100644
index 0000000..89bf3f4
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/LinkELContextTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import javax.el.ExpressionFactory;
+import javax.el.ValueExpression;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+public class LinkELContextTest {
+
+ @Test
+ public void testExpressionFactory() {
+ System.out.println("Create expression factory");
+ ExpressionFactory factory = ExpressionFactory.newInstance();
+ assertNotNull(factory);
+ }
+
+ @Test
+ public void testLiteralExpression() {
+ System.out.println("Literal expression");
+ ExpressionFactory factory = ExpressionFactory.newInstance();
+ LinkELContext context = new LinkELContext(new BooleanBean(), null);
+ ValueExpression expr = factory.createValueExpression(context,
+ "${1+2}", int.class);
+ Object value = expr.getValue(context);
+ assertEquals(3, value);
+ }
+
+ public static final String ID = "10";
+ public static final String NAME = "TheName";
+
+ public static class EntityBean {
+
+ private String id = ID;
+ private String name = NAME;
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ @Test
+ public void testExpression() {
+ System.out.println("Raw expression");
+ ExpressionFactory factory = ExpressionFactory.newInstance();
+ LinkELContext context = new LinkELContext(new EntityBean(), null);
+ ValueExpression expr = factory.createValueExpression(context,
+ "${entity.id}", String.class);
+ Object value = expr.getValue(context);
+ assertEquals(ID, value);
+ }
+
+ @Test
+ public void testEmbeddedExpression() {
+ System.out.println("Embedded expression");
+ ExpressionFactory factory = ExpressionFactory.newInstance();
+ LinkELContext context = new LinkELContext(new EntityBean(), null);
+ ValueExpression expr = factory.createValueExpression(context,
+ "foo/${entity.id}/bar", String.class);
+ Object value = expr.getValue(context);
+ assertEquals("foo/" + ID + "/bar", value);
+ }
+
+ @Test
+ public void testMultipleExpressions() {
+ System.out.println("Multiple expressions");
+ ExpressionFactory factory = ExpressionFactory.newInstance();
+ LinkELContext context = new LinkELContext(new EntityBean(), null);
+ ValueExpression expr = factory.createValueExpression(context,
+ "foo/${entity.id}/bar/${entity.name}", String.class);
+ Object value = expr.getValue(context);
+ assertEquals("foo/" + ID + "/bar/" + NAME, value);
+ }
+
+ public static class OuterEntityBean {
+
+ private EntityBean inner = new EntityBean();
+
+ public EntityBean getInner() {
+ return inner;
+ }
+ }
+
+ @Test
+ public void testNestedExpression() {
+ System.out.println("Nested expression");
+ ExpressionFactory factory = ExpressionFactory.newInstance();
+ LinkELContext context = new LinkELContext(new OuterEntityBean(), null);
+ ValueExpression expr = factory.createValueExpression(context,
+ "${entity.inner.id}", String.class);
+ Object value = expr.getValue(context);
+ assertEquals(ID, value);
+ }
+
+ public static class BooleanBean {
+
+ public boolean getEnabled() {
+ return true;
+ }
+
+ public boolean getValue(boolean value) {
+ return value;
+ }
+ }
+
+ @Test
+ public void testBooleanExpression() {
+ System.out.println("Boolean expression");
+ ExpressionFactory factory = ExpressionFactory.newInstance();
+ LinkELContext context = new LinkELContext(new BooleanBean(), null);
+ ValueExpression expr = factory.createValueExpression(context,
+ "${entity.enabled}", boolean.class);
+ Object value = expr.getValue(context);
+ assertEquals(true, value);
+ expr = factory.createValueExpression(context,
+ "${entity.getValue(true)}", boolean.class);
+ value = expr.getValue(context);
+ assertEquals(true, value);
+ expr = factory.createValueExpression(context,
+ "${entity.getValue(false)}", boolean.class);
+ value = expr.getValue(context);
+ assertEquals(false, value);
+ }
+
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/LinkingManualTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/LinkingManualTest.java
new file mode 100644
index 0000000..6375042
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/LinkingManualTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration;
+
+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 org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.linking.integration.app.LinkingManualApplication;
+import org.glassfish.jersey.linking.integration.representations.OrderRequest;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+
+public class LinkingManualTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ return new LinkingManualApplication();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+
+ }
+
+ @Test
+ public void orderContainsManualLink() throws Exception {
+ OrderRequest request = new OrderRequest();
+ request.setDrink("Coffee");
+ Response response = target().path("/orders").request()
+ .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ String order = response.readEntity(String.class);
+ JSONAssert.assertEquals("{id:'123',price:'1.99',links:["
+ + "{uri:'/',params:{rel:'root'},uriBuilder:{absolute:false},rel:'root',rels:['root']}"
+ + "]}",
+ order, true);
+ }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/LinkingTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/LinkingTest.java
new file mode 100644
index 0000000..d125faf
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/LinkingTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration;
+
+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 org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.linking.integration.app.LinkingApplication;
+import org.glassfish.jersey.linking.integration.representations.OrderRequest;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+
+public class LinkingTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ return new LinkingApplication();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+
+ }
+
+ @Test
+ public void orderContainsProvidedLinks() throws Exception {
+ OrderRequest request = new OrderRequest();
+ request.setDrink("Coffee");
+ Response response = target().path("/orders").request()
+ .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ String order = response.readEntity(String.class);
+ JSONAssert.assertEquals("{id:'123',price:'1.99',links:["
+ + "{uri:'/orders/123',params:{rel:'self'},uriBuilder:{absolute:false},rel:'self',rels:['self']},"
+ + "{uri:'/payments/order/123',params:{rel:'pay'},uriBuilder:{absolute:false},rel:'pay',rels:['pay']},"
+ + "{uri:'/',params:{rel:'root'},uriBuilder:{absolute:false},rel:'root',rels:['root']}"
+ + "]}",
+ order, true);
+ }
+
+ @Test
+ public void providedLinksSupportConditions() throws Exception {
+ OrderRequest request = new OrderRequest();
+ request.setDrink("Water");
+ Response response = target().path("/orders").request()
+ .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ String order = response.readEntity(String.class);
+ JSONAssert.assertEquals("{id:'123',price:'0.0',links:["
+ + "{uri:'/orders/123',params:{rel:'self'},uriBuilder:{absolute:false},rel:'self',rels:['self']},"
+ + "{uri:'/',params:{rel:'root'},uriBuilder:{absolute:false},rel:'root',rels:['root']}"
+ + "]}",
+ order, true);
+ }
+
+ @Test
+ public void metaAnnotationsCanBeUsedToAbstractCommonBehavior_1() throws Exception {
+ Response response = target().path("/orders").request()
+ .get();
+
+ String order = response.readEntity(String.class);
+ JSONAssert.assertEquals("{number:0,size:2,totalPages:3,numberOfElements:0,totalElements:6,links:["
+ + "{uri:'/orders',params:{rel:'create'},uriBuilder:{absolute:false},rels:['create'],rel:'create'},"
+ + "{uri:'/orders?page=1&size=2',params:{rel:'next'},uriBuilder:{absolute:false},rels:['next'],rel:'next'}"
+ + "],orders:["
+ + "{id:'1',price:'1.99',links:["
+ + "{uri:'/orders/1',params:{rel:'self'},uriBuilder:{absolute:false},rels:['self'],rel:'self'},"
+ + "{uri:'/payments/order/1',params:{rel:'pay'},uriBuilder:{absolute:false},rels:['pay'],rel:'pay'}]},"
+ + "{id:'2',price:'0.0',links:["
+ + "{uri:'/orders/2',params:{rel:'self'},uriBuilder:{absolute:false},rels:['self'],rel:'self'}]}"
+ + "],firstPage:true,previousPageAvailable:false,nextPageAvailable:true,lastPage:false}",
+ order, true);
+ }
+
+ @Test
+ public void metaAnnotationsCanBeUsedToAbstractCommonBehavior_2() throws Exception {
+ Response response = target().path("/orders").queryParam("page", "1").request()
+ .get();
+
+ String order = response.readEntity(String.class);
+ JSONAssert.assertEquals("{number:1,size:2,totalPages:3,numberOfElements:0,totalElements:6,links:["
+ + "{uri:'/orders',params:{rel:'create'},uriBuilder:{absolute:false},rels:['create'],rel:'create'},"
+ + "{uri:'/orders?page=2&size=2',params:{rel:'next'},uriBuilder:{absolute:false},rels:['next'],rel:'next'},"
+ + "{uri:'/orders?page=0&size=2',params:{rel:'prev'},uriBuilder:{absolute:false},rels:['prev'],rel:'prev'}"
+ + "],orders:["
+ + "{id:'3',price:'1.99',links:["
+ + "{uri:'/orders/3',params:{rel:'self'},uriBuilder:{absolute:false},rels:['self'],rel:'self'},"
+ + "{uri:'/payments/order/3',params:{rel:'pay'},uriBuilder:{absolute:false},rels:['pay'],rel:'pay'}]},"
+ + "{id:'4',price:'0.0',links:["
+ + "{uri:'/orders/4',params:{rel:'self'},uriBuilder:{absolute:false},rels:['self'],rel:'self'}]}"
+ + "],firstPage:false,previousPageAvailable:true,nextPageAvailable:true,lastPage:false}",
+ order, true);
+ }
+
+ @Test
+ public void metaAnnotationsCanBeUsedToAbstractCommonBehavior_3() throws Exception {
+ Response response = target().path("/orders").queryParam("page", "2").request()
+ .get();
+
+ String order = response.readEntity(String.class);
+ JSONAssert.assertEquals("{number:2,size:2,totalPages:3,numberOfElements:0,totalElements:6,links:["
+ + "{uri:'/orders',params:{rel:'create'},uriBuilder:{absolute:false},rels:['create'],rel:'create'},"
+ + "{uri:'/orders?page=1&size=2',params:{rel:'prev'},uriBuilder:{absolute:false},rels:['prev'],rel:'prev'}"
+ + "],orders:["
+ + "{id:'5',price:'1.99',links:["
+ + "{uri:'/orders/5',params:{rel:'self'},uriBuilder:{absolute:false},rels:['self'],rel:'self'},"
+ + "{uri:'/payments/order/5',params:{rel:'pay'},uriBuilder:{absolute:false},rels:['pay'],rel:'pay'}]},"
+ + "{id:'6',price:'0.0',links:["
+ + "{uri:'/orders/6',params:{rel:'self'},uriBuilder:{absolute:false},rels:['self'],rel:'self'}]}"
+ + "],firstPage:false,previousPageAvailable:true,nextPageAvailable:false,lastPage:true}",
+ order, true);
+ }
+
+
+ @Test
+ public void provideCanBeUsedInConjunctionWithInject() throws Exception {
+ Response response = target().path("/payments/p-1").request().get();
+ String order = response.readEntity(String.class);
+ JSONAssert.assertEquals("{id:'p-1',orderId:'1',links:["
+ + "{uri:'/payments/p-1',params:{rel:'self'},uriBuilder:{absolute:false},rel:'self',rels:['self']},"
+ + "{uri:'/orders/1',params:{rel:'order'},uriBuilder:{absolute:false},rel:'order',rels:['order']}]}",
+ order, true);
+ }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/LinkingApplication.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/LinkingApplication.java
new file mode 100644
index 0000000..dc010c2
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/LinkingApplication.java
@@ -0,0 +1,35 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration.app;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.glassfish.jersey.linking.DeclarativeLinkingFeature;
+
+public class LinkingApplication extends LinkingManualApplication {
+
+ private static final Set<Object> singletons = new HashSet<>(Arrays.asList(
+ new DeclarativeLinkingFeature(),
+ new ObjectMapperContextResolver()));
+
+ @Override
+ public Set<Object> getSingletons() {
+ return singletons;
+ }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/LinkingManualApplication.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/LinkingManualApplication.java
new file mode 100644
index 0000000..b5a498a
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/LinkingManualApplication.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration.app;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.core.Application;
+
+public class LinkingManualApplication extends Application {
+
+ private static final Set<Class<?>> classes = new HashSet<>(Arrays.asList(OrdersResource.class, PaymentResource.class));
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ return classes;
+ }
+
+ @Override
+ public Set<Object> getSingletons() {
+ return Collections.singleton(new ObjectMapperContextResolver());
+ }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/ObjectMapperContextResolver.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/ObjectMapperContextResolver.java
new file mode 100644
index 0000000..d12e369
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/ObjectMapperContextResolver.java
@@ -0,0 +1,43 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration.app;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.ext.ContextResolver;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
+ @Context
+ private HttpHeaders headers;
+
+ private final ObjectMapper mapper;
+
+ public ObjectMapperContextResolver() {
+ mapper = new ObjectMapper();
+
+ // serialization behaviour
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
+ }
+
+ @Override
+ public ObjectMapper getContext(Class<?> type) {
+ return mapper;
+ }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/OrdersResource.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/OrdersResource.java
new file mode 100644
index 0000000..27d559d
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/OrdersResource.java
@@ -0,0 +1,124 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration.app;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+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.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Link;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.glassfish.jersey.linking.Binding;
+import org.glassfish.jersey.linking.ProvideLink;
+import org.glassfish.jersey.linking.integration.representations.ExtendedOrder;
+import org.glassfish.jersey.linking.integration.representations.Order;
+import org.glassfish.jersey.linking.integration.representations.OrderPage;
+import org.glassfish.jersey.linking.integration.representations.OrderRequest;
+import org.glassfish.jersey.linking.integration.representations.PageLinks;
+import org.glassfish.jersey.linking.integration.representations.PaymentConfirmation;
+
+
+@Path("/orders")
+public class OrdersResource {
+
+ @Context
+ private UriInfo uriInfo;
+
+
+ @ProvideLink(value = OrderPage.class, rel = "create")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @POST
+ public Response create(OrderRequest request) {
+ Order order = new Order();
+ order.setId("123");
+ if ("water".equalsIgnoreCase(request.getDrink())) {
+ order.setPrice("0.0");
+ } else {
+ order.setPrice("1.99");
+ }
+
+ order.getLinks().add(Link.fromUri("/").rel("root").build());
+ return Response.ok(order).build();
+ }
+
+ @ProvideLink(value = Order.class, rel = "self", bindings = @Binding(name = "orderId", value = "${instance.id}"))
+ @ProvideLink(value = PaymentConfirmation.class, rel = "order",
+ bindings = @Binding(name = "orderId", value = "${instance.orderId}"))
+ @Produces(MediaType.APPLICATION_JSON)
+ @GET
+ @Path("/{orderId}")
+ public Response get(@PathParam("orderId") String orderId) {
+ ExtendedOrder order = new ExtendedOrder();
+ order.setId("123");
+ order.setPrice("1.99");
+ return Response.ok(order).build();
+ }
+
+
+ @ProvideLink(value = ExtendedOrder.class, rel = "delete",
+ bindings = @Binding(name = "orderId", value = "${instance.id}"))
+ @DELETE
+ @Path("/{orderId}")
+ public Response delete(@PathParam("orderId") String orderId) {
+ return Response.noContent().build();
+ }
+
+
+ @PageLinks(OrderPage.class)
+ @Produces(MediaType.APPLICATION_JSON)
+ @GET
+ public Response list(@QueryParam("page") @DefaultValue("0") int page, @QueryParam("size") @DefaultValue("2") int size) {
+ OrderPage orderPage = new OrderPage();
+
+ orderPage.setFirstPage(page == 0);
+ orderPage.setLastPage(page == 2);
+ orderPage.setPreviousPageAvailable(page > 0);
+ orderPage.setNextPageAvailable(page < 2);
+ orderPage.setNumber(page);
+ orderPage.setSize(size);
+ orderPage.setTotalElements(6);
+ orderPage.setTotalPages(3);
+
+ orderPage.setOrders(generateOrders(page, size));
+
+ return Response.ok(orderPage).build();
+ }
+
+ private List<Order> generateOrders(int page, int size) {
+ final int base = page * size;
+ return IntStream.range(1, size + 1).map(x -> x + base).mapToObj(id -> {
+ Order order = new Order();
+ order.setId(Integer.toString(id));
+ order.setPrice(((id & 1) == 1) ? "1.99" : "0.0");
+ return order;
+ }).collect(Collectors.toList());
+ }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/PaymentResource.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/PaymentResource.java
new file mode 100644
index 0000000..a8d33a5
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/PaymentResource.java
@@ -0,0 +1,61 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration.app;
+
+import javax.ws.rs.Consumes;
+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.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.linking.Binding;
+import org.glassfish.jersey.linking.ProvideLink;
+import org.glassfish.jersey.linking.integration.representations.Order;
+import org.glassfish.jersey.linking.integration.representations.PaymentConfirmation;
+import org.glassfish.jersey.linking.integration.representations.PaymentDetails;
+
+
+@Path("/payments")
+public class PaymentResource {
+
+
+ @ProvideLink(value = Order.class, rel = "pay", bindings = {
+ @Binding(name = "orderId", value = "${instance.id}")}, condition = "${instance.price != '0.0'}")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ @PUT
+ @Path("/order/{orderId}")
+ public Response pay(@PathParam("orderId") String orderId, PaymentDetails paymentDetails) {
+ PaymentConfirmation paymentConfirmation = new PaymentConfirmation();
+ paymentConfirmation.setOrderId(orderId);
+ paymentConfirmation.setId("p-" + orderId);
+ return Response.ok(paymentConfirmation).build();
+ }
+
+ @Produces(MediaType.APPLICATION_JSON)
+ @GET
+ @Path("{id}")
+ public Response getConfirmation(@PathParam("id") String id) {
+ PaymentConfirmation paymentConfirmation = new PaymentConfirmation();
+ paymentConfirmation.setId(id);
+ paymentConfirmation.setOrderId(id.substring(2));
+ return Response.ok(paymentConfirmation).build();
+ }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/ExtendedOrder.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/ExtendedOrder.java
new file mode 100644
index 0000000..934f18f
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/ExtendedOrder.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration.representations;
+
+public class ExtendedOrder extends Order {
+
+ private String details;
+
+ public String getDetails() {
+ return details;
+ }
+
+ public void setDetails(String details) {
+ this.details = details;
+ }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/Order.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/Order.java
new file mode 100644
index 0000000..4daf27d
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/Order.java
@@ -0,0 +1,67 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration.representations;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.linking.InjectLinks;
+
+public class Order {
+
+ private String id;
+
+ private String drink;
+
+ private String price;
+
+ @InjectLinks
+ private List<Link> links = new ArrayList<>();
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getDrink() {
+ return drink;
+ }
+
+ public void setDrink(String drink) {
+ this.drink = drink;
+ }
+
+ public String getPrice() {
+ return price;
+ }
+
+ public void setPrice(String price) {
+ this.price = price;
+ }
+
+ public List<Link> getLinks() {
+ return links;
+ }
+
+ public void setLinks(List<Link> links) {
+ this.links = links;
+ }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/OrderPage.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/OrderPage.java
new file mode 100644
index 0000000..3aef37c
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/OrderPage.java
@@ -0,0 +1,33 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration.representations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class OrderPage extends Page {
+
+ private List<Order> orders = new ArrayList<>();
+
+ public List<Order> getOrders() {
+ return orders;
+ }
+
+ public void setOrders(List<Order> orders) {
+ this.orders = orders;
+ }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/OrderRequest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/OrderRequest.java
new file mode 100644
index 0000000..4af0a9d
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/OrderRequest.java
@@ -0,0 +1,29 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration.representations;
+
+public class OrderRequest {
+ private String drink;
+
+ public String getDrink() {
+ return drink;
+ }
+
+ public void setDrink(String drink) {
+ this.drink = drink;
+ }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/Page.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/Page.java
new file mode 100644
index 0000000..8e1f1ba
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/Page.java
@@ -0,0 +1,154 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration.representations;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.linking.InjectLinks;
+
+public class Page {
+
+ /**
+ * The number of the current page. Is always non-negative and less that {@code Page#getTotalPages()}.
+ */
+ private int number;
+
+ /**
+ * The size of the page.
+ */
+ private int size;
+
+ /**
+ * The number of total pages.
+ */
+ private int totalPages;
+
+ /**
+ * The number of elements currently on this page.
+ */
+ private int numberOfElements;
+
+ /**
+ * The total amount of elements.
+ */
+ private long totalElements;
+
+ /**
+ * If there is a previous page.
+ */
+ private boolean isPreviousPageAvailable;
+
+ /**
+ * Whether the current page is the first one.
+ */
+ private boolean isFirstPage;
+
+ /**
+ * If there is a next page.
+ */
+ private boolean isNextPageAvailable;
+
+ /**
+ * Whether the current page is the last one.
+ */
+ private boolean isLastPage;
+
+ @InjectLinks
+ private List<Link> links = new ArrayList<>();
+
+ public int getNumber() {
+ return number;
+ }
+
+ public void setNumber(int number) {
+ this.number = number;
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public void setSize(int size) {
+ this.size = size;
+ }
+
+ public int getTotalPages() {
+ return totalPages;
+ }
+
+ public void setTotalPages(int totalPages) {
+ this.totalPages = totalPages;
+ }
+
+ public int getNumberOfElements() {
+ return numberOfElements;
+ }
+
+ public void setNumberOfElements(int numberOfElements) {
+ this.numberOfElements = numberOfElements;
+ }
+
+ public long getTotalElements() {
+ return totalElements;
+ }
+
+ public void setTotalElements(long totalElements) {
+ this.totalElements = totalElements;
+ }
+
+ public boolean isPreviousPageAvailable() {
+ return isPreviousPageAvailable;
+ }
+
+ public void setPreviousPageAvailable(boolean previousPageAvailable) {
+ isPreviousPageAvailable = previousPageAvailable;
+ }
+
+ public boolean isFirstPage() {
+ return isFirstPage;
+ }
+
+ public void setFirstPage(boolean firstPage) {
+ isFirstPage = firstPage;
+ }
+
+ public boolean isNextPageAvailable() {
+ return isNextPageAvailable;
+ }
+
+ public void setNextPageAvailable(boolean nextPageAvailable) {
+ isNextPageAvailable = nextPageAvailable;
+ }
+
+ public boolean isLastPage() {
+ return isLastPage;
+ }
+
+ public void setLastPage(boolean lastPage) {
+ isLastPage = lastPage;
+ }
+
+ public List<Link> getLinks() {
+ return links;
+ }
+
+ public void setLinks(List<Link> links) {
+ this.links = links;
+ }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PageLinks.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PageLinks.java
new file mode 100644
index 0000000..421ba52
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PageLinks.java
@@ -0,0 +1,41 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration.representations;
+
+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.linking.Binding;
+import org.glassfish.jersey.linking.ProvideLink;
+
+@ProvideLink(value = ProvideLink.InheritFromAnnotation.class, rel = "next", bindings = {
+ @Binding(name = "page", value = "${instance.number + 1}"),
+ @Binding(name = "size", value = "${instance.size}"),
+}, condition = "${instance.nextPageAvailable}")
+@ProvideLink(value = ProvideLink.InheritFromAnnotation.class, rel = "prev", bindings = {
+ @Binding(name = "page", value = "${instance.number - 1}"),
+ @Binding(name = "size", value = "${instance.size}"),
+}, condition = "${instance.previousPageAvailable}")
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface PageLinks {
+ Class<?> value();
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PaymentConfirmation.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PaymentConfirmation.java
new file mode 100644
index 0000000..869dd90
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PaymentConfirmation.java
@@ -0,0 +1,61 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration.representations;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.linking.InjectLink;
+import org.glassfish.jersey.linking.InjectLinks;
+import org.glassfish.jersey.linking.integration.app.PaymentResource;
+
+public class PaymentConfirmation {
+
+ private String id;
+
+ private String orderId;
+
+ @InjectLinks(
+ @InjectLink(resource = PaymentResource.class, method = "getConfirmation", rel = "self")
+ )
+ private List<Link> links = new ArrayList<>();
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getOrderId() {
+ return orderId;
+ }
+
+ public void setOrderId(String orderId) {
+ this.orderId = orderId;
+ }
+
+ public List<Link> getLinks() {
+ return links;
+ }
+
+ public void setLinks(List<Link> links) {
+ this.links = links;
+ }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PaymentDetails.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PaymentDetails.java
new file mode 100644
index 0000000..284f204
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PaymentDetails.java
@@ -0,0 +1,56 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking.integration.representations;
+
+public class PaymentDetails {
+ private String creditCardNumber;
+ private String cve;
+ private String name;
+ private String expiration;
+
+ public String getCreditCardNumber() {
+ return creditCardNumber;
+ }
+
+ public void setCreditCardNumber(String creditCardNumber) {
+ this.creditCardNumber = creditCardNumber;
+ }
+
+ public String getCve() {
+ return cve;
+ }
+
+ public void setCve(String cve) {
+ this.cve = cve;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getExpiration() {
+ return expiration;
+ }
+
+ public void setExpiration(String expiration) {
+ this.expiration = expiration;
+ }
+}
diff --git a/incubator/gae-integration/pom.xml b/incubator/gae-integration/pom.xml
new file mode 100644
index 0000000..f692c90
--- /dev/null
+++ b/incubator/gae-integration/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v. 2.0, which is available at
+ http://www.eclipse.org/legal/epl-2.0.
+
+ This Source Code may also be made available under the following Secondary
+ Licenses when the conditions for such availability set forth in the
+ Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ version 2 with the GNU Classpath Exception, which is available at
+ https://www.gnu.org/software/classpath/license.html.
+
+ SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<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.incubator</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-gae-integration</artifactId>
+ <name>jersey-ext-gae-integration</name>
+
+ <description>
+ Jersey extension module providing support for Google App Engine integration.
+ NOTE: IT DOES NOT WORK YET BECAUSE OF GAE SERVLET 2.5 (see https://java.net/jira/browse/JERSEY-1853)!!!
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.appengine</groupId>
+ <artifactId>appengine-api-1.0-sdk</artifactId>
+ <version>${gae.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/GaeBackgroundExecutorProvider.java b/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/GaeBackgroundExecutorProvider.java
new file mode 100644
index 0000000..0338725
--- /dev/null
+++ b/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/GaeBackgroundExecutorProvider.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.server.gae;
+
+import java.util.concurrent.ThreadFactory;
+
+import org.glassfish.jersey.server.BackgroundScheduler;
+import org.glassfish.jersey.spi.ScheduledThreadPoolExecutorProvider;
+
+/**
+ * This class implements Jersey's SPI {@link org.glassfish.jersey.spi.ScheduledExecutorServiceProvider} to provide a
+ * {@link java.util.concurrent.ScheduledExecutorService} instances with a GAE specific {@link ThreadFactory} provider
+ * - {@link com.google.appengine.api.ThreadManager}.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@BackgroundScheduler
+class GaeBackgroundExecutorProvider extends ScheduledThreadPoolExecutorProvider {
+
+ /**
+ * Create new instance of GAE-specific background scheduled executor service provider.
+ */
+ public GaeBackgroundExecutorProvider() {
+ super("gae-jersey-background-task-scheduler");
+ }
+
+ @Override
+ public ThreadFactory getBackingThreadFactory() {
+ return com.google.appengine.api.ThreadManager.backgroundThreadFactory();
+ }
+
+ @Override
+ protected int getCorePoolSize() {
+ return 1;
+ }
+}
diff --git a/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/GaeFeature.java b/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/GaeFeature.java
new file mode 100644
index 0000000..cc99f84
--- /dev/null
+++ b/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/GaeFeature.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.server.gae;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+/**
+ * Google App Engine integration for Jersey server runtime.
+ * <p>
+ * Register this feature in your {@link org.glassfish.jersey.server.ResourceConfig} subclass in order to be able to run
+ * your Jersey application on Google App Engine.
+ * </p>
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public final class GaeFeature implements Feature {
+
+ @Override
+ public boolean configure(final FeatureContext context) {
+ if (!context.getConfiguration().isRegistered(GaeBackgroundExecutorProvider.class)) {
+ context.register(new GaeBackgroundExecutorProvider());
+ }
+ return true;
+ }
+
+}
diff --git a/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/package-info.java b/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/package-info.java
new file mode 100644
index 0000000..6143916
--- /dev/null
+++ b/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Public API of Google App Engine integration support.
+ *
+ * Register {@link org.glassfish.jersey.server.gae.GaeFeature} in your {@link org.glassfish.jersey.server.ResourceConfig}
+ * subclass to enable the support.
+ */
+package org.glassfish.jersey.server.gae;
diff --git a/incubator/html-json/pom.xml b/incubator/html-json/pom.xml
new file mode 100644
index 0000000..882bb46
--- /dev/null
+++ b/incubator/html-json/pom.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0"?>
+<!--
+
+ 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 Public License v. 2.0, which is available at
+ http://www.eclipse.org/legal/epl-2.0.
+
+ This Source Code may also be made available under the following Secondary
+ Licenses when the conditions for such availability set forth in the
+ Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ version 2 with the GNU Classpath Exception, which is available at
+ https://www.gnu.org/software/classpath/license.html.
+
+ SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<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.incubator</groupId>
+ <artifactId>project</artifactId>
+ <version>2.26-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>html-json</artifactId>
+
+ <name>jersey-media-html-json</name>
+
+ <description>
+ JAX-RS Reader/Writer via net.java.html.json library that provides
+ JSON serialization and deserialization using lightweight (no reflection)
+ net.java.html.json library. Define your objects via @Model annotation.
+ Use them in Jersey.
+ </description>
+
+ <properties>
+ <net.java.html.version>1.0</net.java.html.version>
+ </properties>
+
+ <repositories>
+ <repository>
+ <id>netbeans</id>
+ <name>NetBeans</name>
+ <url>http://bits.netbeans.org/maven2/</url>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.netbeans.html</groupId>
+ <artifactId>net.java.html.json</artifactId>
+ <version>${net.java.html.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.netbeans.api</groupId>
+ <artifactId>org-openide-util-lookup</artifactId>
+ <version>RELEASE80</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-core</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.connectors</groupId>
+ <artifactId>jersey-apache-connector</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>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.netbeans.html</groupId>
+ <artifactId>ko-ws-tyrus</artifactId>
+ <version>${net.java.html.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <!-- Overwrite Grizzly dependency defined in ko-ws-tyrus -->
+ <dependency>
+ <groupId>org.glassfish.grizzly</groupId>
+ <artifactId>grizzly-framework</artifactId>
+ <version>${grizzly2.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/HtmlJsonProvider.java b/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/HtmlJsonProvider.java
new file mode 100644
index 0000000..b220eda
--- /dev/null
+++ b/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/HtmlJsonProvider.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.htmljson;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.WebTarget;
+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 org.openide.util.lookup.ServiceProvider;
+import org.openide.util.lookup.ServiceProviders;
+
+import net.java.html.BrwsrCtx;
+import net.java.html.json.Model;
+import net.java.html.json.Models;
+import net.java.html.json.Property;
+
+/**
+ * Implementation of Jersey's message body reader and writer that
+ * can handle reading and writing of JSON models generated by {@link Model}
+ * annotation provided by
+ * <a target="_blank" href="http://bck2brwsr.apidesign.org/javadoc/net.java.html.json/">net.java.html.json</a>
+ * library. Include
+ * this JAR in your project and you can then use your
+ * model classes as Jersey's entities.
+ * <p>
+ * <pre>
+ * {@link Model @Model}(className="Query", properties={
+ * {@link Property @Property}(name="items", type=Item.<b>class</b>, array=true)
+ * })
+ * <b>class</b> QueryImpl {
+ *
+ * {@link Model @Model}(className="Item", properties={
+ * {@link Property @Property}(name="id", type=String.<b>class</b>),
+ * {@link Property @Property}(name="kind", type=Kind.<b>class</b>)
+ * })
+ * <b>class</b> ItemImpl {
+ * }
+ *
+ * <b>enum</b> Kind {
+ * GOOD, BAD
+ * }
+ *
+ * <b>public static</b> List{@code <Item>} doQuery() {
+ * {@link WebTarget} target = ...;
+ * Query q = target.request(MediaType.APPLICATION_JSON).get().readEntity(Query.<b>class</b>);
+ * return q.getItems();
+ * }
+ * }
+ * </pre>
+ *
+ * @author Jaroslav Tulach (jtulach at netbeans.org)
+ */
+@ServiceProviders({
+ @ServiceProvider(service = MessageBodyWriter.class),
+ @ServiceProvider(service = MessageBodyReader.class)
+})
+public final class HtmlJsonProvider implements MessageBodyWriter<Object>, MessageBodyReader<Object> {
+
+ @Override
+ public boolean isWriteable(Class clazz, Type type, Annotation[] antns, MediaType mt) {
+ if (!mt.isCompatible(MediaType.APPLICATION_JSON_TYPE)) {
+ return false;
+ }
+ if (clazz.isArray()) {
+ return Models.isModel(clazz.getComponentType());
+ }
+ if (java.util.List.class.isAssignableFrom(clazz)) {
+ if (type instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType) type;
+ Type[] args = pt.getActualTypeArguments();
+ if (args.length == 1 && args[0] instanceof Class) {
+ return Models.isModel((Class<?>) args[0]);
+ }
+ }
+ }
+ return Models.isModel(clazz);
+ }
+
+ @Override
+ public long getSize(Object t, Class type, Type type1, Annotation[] antns, MediaType mt) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(Object t, Class type, Type type1, Annotation[] antns, MediaType mt, MultivaluedMap mm, OutputStream out)
+ throws IOException, WebApplicationException {
+ dump(t, out);
+ }
+
+ private void dump(Object t, OutputStream out) throws IOException {
+ if (t instanceof Object[]) {
+ Object[] arr = (Object[]) t;
+ out.write('[');
+ for (int i = 0; i < arr.length; i++) {
+ if (i > 0) {
+ out.write(',');
+ }
+ dump(arr[i], out);
+ }
+ out.write(']');
+ } else {
+ out.write(t.toString().getBytes("UTF-8"));
+ }
+ }
+
+ @Override
+ public boolean isReadable(Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
+ return isWriteable(type, type1, antns, mt);
+ }
+
+ @Override
+ public Object readFrom(Class<Object> clazz,
+ Type type, Annotation[] antns, MediaType mt,
+ MultivaluedMap<String, String> mm,
+ InputStream in) throws IOException, WebApplicationException {
+ BrwsrCtx def = BrwsrCtx.findDefault(HtmlJsonProvider.class);
+ if (clazz.isArray()) {
+ List<Object> res = new ArrayList<>();
+ final Class<?> cmp = clazz.getComponentType();
+ Models.parse(def, cmp, in, res);
+ Object[] arr = (Object[]) Array.newInstance(cmp, res.size());
+ return res.toArray(arr);
+ }
+ if (clazz.isAssignableFrom(java.util.List.class)
+ && type instanceof ParameterizedType
+ && ((ParameterizedType) type).getActualTypeArguments().length == 1
+ && ((ParameterizedType) type).getActualTypeArguments()[0] instanceof Class) {
+ List<Object> res = new ArrayList<>();
+ final Class<?> cmp = (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
+ Models.parse(def, cmp, in, res);
+ return res;
+ }
+ return Models.parse(def, clazz, in);
+ }
+}
diff --git a/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/internal/HtmlJsonAutoDiscoverable.java b/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/internal/HtmlJsonAutoDiscoverable.java
new file mode 100644
index 0000000..7b3b56a
--- /dev/null
+++ b/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/internal/HtmlJsonAutoDiscoverable.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.htmljson.internal;
+
+import javax.annotation.Priority;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.internal.spi.AutoDiscoverable;
+import org.glassfish.jersey.media.htmljson.HtmlJsonProvider;
+
+/**
+ * @author Michal Gajdos
+ */
+@Priority(AutoDiscoverable.DEFAULT_PRIORITY)
+public class HtmlJsonAutoDiscoverable implements AutoDiscoverable {
+
+ @Override
+ public void configure(final FeatureContext context) {
+ final Configuration config = context.getConfiguration();
+
+ if (!config.isRegistered(HtmlJsonProvider.class)) {
+ context.register(HtmlJsonProvider.class);
+ }
+ }
+}
diff --git a/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/package-info.java b/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/package-info.java
new file mode 100644
index 0000000..5a8cf74
--- /dev/null
+++ b/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey package with entity provider that allows usage of {@code net.java.html.json}.
+ */
+package org.glassfish.jersey.media.htmljson;
diff --git a/incubator/html-json/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable b/incubator/html-json/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
new file mode 100644
index 0000000..4709b74
--- /dev/null
+++ b/incubator/html-json/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
@@ -0,0 +1 @@
+org.glassfish.jersey.media.htmljson.internal.HtmlJsonAutoDiscoverable
\ No newline at end of file
diff --git a/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/AbstractTypeTester.java b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/AbstractTypeTester.java
new file mode 100644
index 0000000..5213a80
--- /dev/null
+++ b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/AbstractTypeTester.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.htmljson;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.WebApplicationException;
+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.ext.Provider;
+import javax.ws.rs.ext.WriterInterceptor;
+import javax.ws.rs.ext.WriterInterceptorContext;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.message.internal.ReaderWriter;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Abstract entity type tester base class.
+ *
+ * @author Paul Sandoz
+ * @author Martin Matula
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public abstract class AbstractTypeTester extends JerseyTest {
+
+ protected static byte[] requestEntity;
+
+ public abstract static class AResource<T> {
+
+ @POST
+ public T post(T t) {
+ return t;
+ }
+ }
+
+ public static class RequestEntityInterceptor implements WriterInterceptor {
+
+ @Override
+ public void aroundWriteTo(WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException {
+ OutputStream original = writerInterceptorContext.getOutputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writerInterceptorContext.setOutputStream(baos);
+ writerInterceptorContext.proceed();
+ requestEntity = baos.toByteArray();
+ original.write(requestEntity);
+ }
+ }
+
+ /**
+ * Looks for all resources and providers declared as inner classes of the subclass of this class
+ * and adds them to the returned ResourceConfig (unless constrained to client side).
+ *
+ * @return ResourceConfig instance
+ */
+ @Override
+ protected Application configure() {
+ HashSet<Class<?>> classes = new HashSet<Class<?>>();
+
+ for (Class<?> cls : getClass().getDeclaredClasses()) {
+ if (cls.getAnnotation(Path.class) != null) {
+ classes.add(cls);
+ } else if (cls.getAnnotation(Provider.class) != null) {
+ final ConstrainedTo constrainedTo = cls.getAnnotation(ConstrainedTo.class);
+ if (constrainedTo == null || constrainedTo.value() == RuntimeType.SERVER) {
+ classes.add(cls);
+ }
+ }
+ }
+
+ return new ResourceConfig(classes);
+ }
+
+ /**
+ * Looks for all providers declared as inner classes of the subclass of this class
+ * and adds them to the client configuration (unless constrained to server side).
+ */
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(RequestEntityInterceptor.class);
+
+ for (Class<?> cls : getClass().getDeclaredClasses()) {
+ if (cls.getAnnotation(Provider.class) != null) {
+ final ConstrainedTo constrainedTo = cls.getAnnotation(ConstrainedTo.class);
+ if (constrainedTo == null || constrainedTo.value() == RuntimeType.CLIENT) {
+ config.register(cls);
+ }
+ }
+ }
+ }
+
+ protected <T> void _test(T in, Class resource) {
+ _test(in, resource, true);
+ }
+
+ protected <T> void _test(T in, Class resource, MediaType m) {
+ _test(in, resource, m, true);
+ }
+
+ protected <T> void _test(T in, Class resource, boolean verify) {
+ _test(in, resource, MediaType.TEXT_PLAIN_TYPE, verify);
+ }
+
+ protected <T> void _test(T in, Class resource, MediaType m, boolean verify) {
+ WebTarget target = target(resource.getSimpleName());
+ Response response = target.request().post(Entity.entity(in, m));
+
+ byte[] inBytes = requestEntity;
+ byte[] outBytes = getEntityAsByteArray(response);
+
+ if (verify) {
+ _verify(inBytes, outBytes);
+ }
+ }
+
+ protected static void _verify(byte[] in, byte[] out) {
+ assertEquals(in.length, out.length);
+ for (int i = 0; i < in.length; i++) {
+ if (in[i] != out[i]) {
+ assertEquals("Index: " + i, in[i], out[i]);
+ }
+ }
+ }
+
+ protected static byte[] getEntityAsByteArray(Response r) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ ReaderWriter.writeTo(r.readEntity(InputStream.class), baos);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+}
diff --git a/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ComputedPropertyTest.java b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ComputedPropertyTest.java
new file mode 100644
index 0000000..eb5a839
--- /dev/null
+++ b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ComputedPropertyTest.java
@@ -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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.htmljson;
+
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+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.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+import net.java.html.json.ComputedProperty;
+import net.java.html.json.Model;
+import net.java.html.json.Property;
+
+/** Can use the same code on server as well as client.
+ *
+ * @author Jaroslav Tulach
+ */
+@Model(className = "Person", properties = {
+ @Property(name = "firstName", type = String.class),
+ @Property(name = "lastName", type = String.class)
+})
+public class ComputedPropertyTest extends AbstractTypeTester {
+
+ @ComputedProperty
+ static String fullName(String firstName, String lastName) {
+ return firstName + " " + lastName;
+ }
+
+ //
+ // server side
+ //
+ @Path("empty")
+ public static class TestResource {
+
+ @PUT
+ @Path("fullName")
+ public String myBean(Person p) {
+ return p.getFullName();
+ }
+ }
+
+ //
+ // Client using the model to connect to server
+ //
+
+ @Test
+ public void askForFullName() {
+ WebTarget target = target("empty/fullName");
+
+ Person p = new Person();
+ p.setFirstName("Jaroslav");
+ p.setLastName("Tulach");
+
+ final Response response = target.request().put(Entity.entity(p, MediaType.APPLICATION_JSON_TYPE));
+
+ assertEquals(200, response.getStatus());
+ assertEquals("Jaroslav Tulach", response.readEntity(String.class));
+ }
+}
diff --git a/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityOnArrayTest.java b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityOnArrayTest.java
new file mode 100644
index 0000000..a1e01c7
--- /dev/null
+++ b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityOnArrayTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.htmljson;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+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.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import net.java.html.json.Model;
+
+/**
+ * Reading and writing class generated by {@link Model} as
+ * arrays.
+ *
+ * @author Jaroslav Tulach
+ */
+public class ModelEntityOnArrayTest extends AbstractTypeTester {
+
+ @Path("empty")
+ public static class TestResource {
+
+ @POST
+ @Path("mybean")
+ public String myBean(MyBean[] bean) {
+ if (bean.length != 2) {
+ return "ERROR, length: " + bean.length;
+ }
+ if (!bean[0].getValue().equals("Hello")) {
+ return "ERROR, [0].value = " + bean[0].getValue();
+ }
+ if (!bean[1].getValue().equals("Ahoy")) {
+ return "ERROR, [1].value = " + bean[1].getValue();
+ }
+ return "PASSED";
+ }
+
+ @GET
+ @Path("getbean")
+ public Response getBean(@Context HttpHeaders headers) {
+ MyBean teb = new MyBean();
+ teb.setValue("hello");
+ return Response.ok().type(MediaType.APPLICATION_JSON_TYPE).entity(new MyBean[] {teb}).build();
+ }
+ }
+
+ public ModelEntityOnArrayTest() {
+ enable(TestProperties.LOG_TRAFFIC);
+ }
+
+ @Test
+ public void myBeanAndPut() {
+ WebTarget target = target("empty/mybean");
+
+ MyBean mb = new MyBean();
+ mb.setValue("Hello");
+ MyBean ah = new MyBean();
+ ah.setValue("Ahoy");
+ MyBean[] arr = new MyBean[] {mb, ah};
+
+ final Response response = target.request().post(Entity.entity(arr, MediaType.APPLICATION_JSON_TYPE));
+
+ assertEquals(200, response.getStatus());
+ assertEquals("PASSED", response.readEntity(String.class));
+ }
+
+ @Test
+ public void doReadWrite() throws Exception {
+ MyBean mb = new MyBean();
+ mb.setValue("Hello");
+ MyBean ah = new MyBean();
+ ah.setValue("Ahoy");
+ MyBean[] arr = new MyBean[] {mb, ah};
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ os.write('[');
+ os.write(arr[0].toString().getBytes("UTF-8"));
+ os.write(',');
+ os.write(arr[1].toString().getBytes("UTF-8"));
+ os.write(']');
+ os.close();
+
+ ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+
+ final Class c = arr.getClass();
+ Object ret = new HtmlJsonProvider().readFrom(c, null, null, MediaType.APPLICATION_JSON_TYPE, null, is);
+
+ assertTrue("It is array: " + ret, ret instanceof MyBean[]);
+ MyBean[] res = (MyBean[]) ret;
+ assertEquals("Two items: ", 2, res.length);
+ assertEquals(arr[0], res[0]);
+ assertEquals(arr[1], res[1]);
+ }
+
+ @Test
+ public void myBeanAndGet() {
+ WebTarget target = target("empty/getbean");
+ final Response response = target.request(MediaType.APPLICATION_JSON).get();
+ assertEquals(200, response.getStatus());
+ final MyBean[] teb = response.readEntity(MyBean[].class);
+
+ assertEquals("value", "hello", teb[0].getValue());
+ }
+}
diff --git a/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityOnListTest.java b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityOnListTest.java
new file mode 100644
index 0000000..2dffdb7
--- /dev/null
+++ b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityOnListTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.htmljson;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+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.core.Context;
+import javax.ws.rs.core.GenericEntity;
+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.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+import net.java.html.json.Model;
+
+/**
+ * Reading and writing class generated by {@link Model} as
+ * {@link List}.
+ *
+ * @author Jaroslav Tulach
+ */
+public class ModelEntityOnListTest extends AbstractTypeTester {
+
+ @Path("empty")
+ public static class TestResource {
+
+ @POST
+ @Path("mybean")
+ public String myBean(List<MyBean> bean) {
+ if (bean.size() != 2) {
+ return "ERROR, length: " + bean.size();
+ }
+ if (!bean.get(0).getValue().equals("Hello")) {
+ return "ERROR, [0].value = " + bean.get(0).getValue();
+ }
+ if (!bean.get(1).getValue().equals("Ahoy")) {
+ return "ERROR, [1].value = " + bean.get(1).getValue();
+ }
+ return "PASSED";
+ }
+
+ @GET
+ @Path("getbean")
+ public Response getBean(@Context HttpHeaders headers) {
+ MyBean teb = new MyBean();
+ teb.setValue("hello");
+ List<MyBean> l = new ArrayList<MyBean>();
+ l.add(teb);
+ GenericEntity<List<MyBean>> ge = new GenericEntity<List<MyBean>>(l) {};
+ return Response.ok().type(MediaType.APPLICATION_JSON_TYPE).entity(ge).build();
+ }
+ }
+
+ public ModelEntityOnListTest() {
+ enable(TestProperties.LOG_TRAFFIC);
+ }
+
+ @Test
+ public void myBeanAndPut() {
+ WebTarget target = target("empty/mybean");
+
+ MyBean mb = new MyBean();
+ mb.setValue("Hello");
+ MyBean ah = new MyBean();
+ ah.setValue("Ahoy");
+ GenericEntity<List<MyBean>> ge = new GenericEntity<List<MyBean>>(Arrays.asList(mb, ah)) {};
+ final Response response = target.request().post(Entity.entity(ge, MediaType.APPLICATION_JSON_TYPE));
+
+ assertEquals(200, response.getStatus());
+ assertEquals("PASSED", response.readEntity(String.class));
+ }
+
+ @Test
+ public void myBeanAndGet() {
+ WebTarget target = target("empty/getbean");
+ final Response response = target.request(MediaType.APPLICATION_JSON).get();
+ assertEquals(200, response.getStatus());
+ GenericType<List<MyBean>> ge = new GenericType<List<MyBean>>() {};
+ List<MyBean> teb = response.readEntity(ge);
+
+ assertEquals("one element in list: " + teb, 1, teb.size());
+ assertEquals("value", "hello", teb.get(0).getValue());
+ }
+}
diff --git a/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityTest.java b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityTest.java
new file mode 100644
index 0000000..dfc996f
--- /dev/null
+++ b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.htmljson;
+
+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.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+import net.java.html.json.Model;
+import net.java.html.json.Property;
+
+/**
+ * Reading and writing class generated by {@link Model}.
+ *
+ * @author Jaroslav Tulach
+ */
+public class ModelEntityTest extends AbstractTypeTester {
+
+ @Model(className = "MyBean", properties = {
+ @Property(name = "value", type = String.class)
+ })
+ static class MB {
+ }
+
+ @Path("empty")
+ public static class TestResource {
+ @POST
+ @Path("mybean")
+ public String myBean(MyBean bean) {
+ return (bean.getValue().equals("Hello")) ? "PASSED" : "ERROR";
+ }
+
+ @GET
+ @Path("getbean")
+ public Response getBean(@Context HttpHeaders headers) {
+ MyBean teb = new MyBean();
+ teb.setValue("hello");
+ return Response.ok().type(MediaType.APPLICATION_JSON_TYPE).entity(teb).build();
+ }
+ }
+
+ public ModelEntityTest() {
+ enable(TestProperties.LOG_TRAFFIC);
+ }
+
+ @Test
+ public void myBeanAndPut() {
+ WebTarget target = target("empty/mybean");
+
+ MyBean mb = new MyBean();
+ mb.setValue("Hello");
+
+ final Response response = target.request().post(Entity.entity(mb, MediaType.APPLICATION_JSON_TYPE));
+
+ assertEquals(200, response.getStatus());
+ assertEquals("PASSED", response.readEntity(String.class));
+ }
+
+ @Test
+ public void myBeanAndGet() {
+ WebTarget target = target("empty/getbean");
+ final Response response = target.request(MediaType.APPLICATION_JSON).get();
+ assertEquals(200, response.getStatus());
+ final MyBean teb = response.readEntity(MyBean.class);
+
+ assertEquals("value", "hello", teb.getValue());
+ }
+}
diff --git a/incubator/kryo/README.md b/incubator/kryo/README.md
new file mode 100644
index 0000000..4e10678
--- /dev/null
+++ b/incubator/kryo/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 Public License v. 2.0, which is available at "
+[//]: # " http://www.eclipse.org/legal/epl-2.0. "
+[//]: # " "
+[//]: # " This Source Code may also be made available under the following Secondary "
+[//]: # " Licenses when the conditions for such availability set forth in the "
+[//]: # " Eclipse Public License v. 2.0 are satisfied: GNU General Public License, "
+[//]: # " version 2 with the GNU Classpath Exception, which is available at "
+[//]: # " https://www.gnu.org/software/classpath/license.html. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 "
+
+This is experimental module that provides JAX-RS Message Body Writer & Reader using Kryo serialization framework.
+
+# How to use it
+
+Add dependency to the module:
+
+```xml
+<dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-kryo</artifactId>
+ <version>${jersey.version}</version>
+</dependency>
+```
+
+And now you can consume or produce entities (de)serialized by Kryo. Just use `application/x-kryo` MIME type, e.g.:
+
+```java
+@Path("/rest")
+@Consumes("application/x-kryo")
+@Produces("application/x-kryo")
+public class MyResource { ... }
+```
diff --git a/incubator/kryo/pom.xml b/incubator/kryo/pom.xml
new file mode 100644
index 0000000..a6a7757
--- /dev/null
+++ b/incubator/kryo/pom.xml
@@ -0,0 +1,60 @@
+<?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 Public License v. 2.0, which is available at
+ http://www.eclipse.org/legal/epl-2.0.
+
+ This Source Code may also be made available under the following Secondary
+ Licenses when the conditions for such availability set forth in the
+ Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ version 2 with the GNU Classpath Exception, which is available at
+ https://www.gnu.org/software/classpath/license.html.
+
+ SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<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.incubator</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-kryo</artifactId>
+ <name>jersey-media-kryo</name>
+
+ <description>
+ Jersey/JAX-RS Message Body Writer and Reader using Kryo serialization framework
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.esotericsoftware</groupId>
+ <artifactId>kryo</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/KryoFeature.java b/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/KryoFeature.java
new file mode 100644
index 0000000..63545fa
--- /dev/null
+++ b/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/KryoFeature.java
@@ -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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.kryo;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.Beta;
+import org.glassfish.jersey.kryo.internal.KryoMessageBodyProvider;
+
+/**
+ * Feature used to register Kryo providers.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+@Beta
+public class KryoFeature implements Feature {
+
+ @Override
+ public boolean configure(final FeatureContext context) {
+ final Configuration config = context.getConfiguration();
+
+ if (!config.isRegistered(KryoMessageBodyProvider.class)) {
+ context.register(KryoMessageBodyProvider.class);
+ }
+
+ return true;
+ }
+
+}
diff --git a/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/internal/KryoAutoDiscoverable.java b/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/internal/KryoAutoDiscoverable.java
new file mode 100644
index 0000000..73107ca
--- /dev/null
+++ b/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/internal/KryoAutoDiscoverable.java
@@ -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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.kryo.internal;
+
+import javax.ws.rs.core.FeatureContext;
+
+import javax.annotation.Priority;
+
+import org.glassfish.jersey.internal.spi.AutoDiscoverable;
+import org.glassfish.jersey.kryo.KryoFeature;
+
+/**
+ * {@link AutoDiscoverable} registering {@link KryoFeature} if the feature is not already registered.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+@Priority(AutoDiscoverable.DEFAULT_PRIORITY)
+public class KryoAutoDiscoverable implements AutoDiscoverable {
+
+ @Override
+ public void configure(final FeatureContext context) {
+ if (!context.getConfiguration().isRegistered(KryoFeature.class)) {
+ context.register(KryoFeature.class);
+ }
+ }
+
+}
diff --git a/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/internal/KryoMessageBodyProvider.java b/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/internal/KryoMessageBodyProvider.java
new file mode 100644
index 0000000..755c290
--- /dev/null
+++ b/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/internal/KryoMessageBodyProvider.java
@@ -0,0 +1,118 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.kryo.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+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 com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.esotericsoftware.kryo.pool.KryoCallback;
+import com.esotericsoftware.kryo.pool.KryoFactory;
+import com.esotericsoftware.kryo.pool.KryoPool;
+
+/**
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+@Provider
+@Consumes("application/x-kryo")
+@Produces("application/x-kryo")
+public class KryoMessageBodyProvider implements MessageBodyWriter<Object>, MessageBodyReader<Object> {
+
+ private final KryoPool kryoPool;
+
+ public KryoMessageBodyProvider() {
+ final KryoFactory kryoFactory = new KryoFactory() {
+ public Kryo create() {
+ final Kryo kryo = new Kryo();
+ //TODO: configure kryo instance, customize settings
+ //TODO: e.g. looking for Kryo via ContextResolver (like Jackson)
+ return kryo;
+ }
+ };
+ kryoPool = new KryoPool.Builder(kryoFactory).softReferences().build();
+ }
+
+ //
+ // MessageBodyWriter
+ //
+
+ @Override
+ public long getSize(final Object object, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public boolean isWriteable(final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return true;
+ }
+
+ @Override
+ public void writeTo(final Object object, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream)
+ throws IOException, WebApplicationException {
+ final Output output = new Output(entityStream);
+ kryoPool.run(new KryoCallback() {
+ public Object execute(Kryo kryo) {
+ kryo.writeObject(output, object);
+ return null;
+ }
+ });
+ output.flush();
+ }
+
+ //
+ // MessageBodyReader
+ //
+
+ @Override
+ public boolean isReadable(final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return true;
+ }
+
+ @Override
+ public Object readFrom(final Class<Object> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, String> httpHeaders,
+ final InputStream entityStream) throws IOException, WebApplicationException {
+ final Input input = new Input(entityStream);
+
+ return kryoPool.run(new KryoCallback() {
+ public Object execute(Kryo kryo) {
+ return kryo.readObject(input, type);
+ }
+ });
+ }
+
+}
diff --git a/incubator/kryo/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable b/incubator/kryo/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
new file mode 100644
index 0000000..870630d
--- /dev/null
+++ b/incubator/kryo/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
@@ -0,0 +1 @@
+org.glassfish.jersey.kryo.internal.KryoAutoDiscoverable
diff --git a/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/JaxRsApplication.java b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/JaxRsApplication.java
new file mode 100644
index 0000000..fae6a4c
--- /dev/null
+++ b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/JaxRsApplication.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.kryo;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.core.Application;
+
+/**
+ * Test case JAX-RS application.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+public class JaxRsApplication extends Application {
+
+ static final Set<Class<?>> APP_CLASSES = new HashSet<Class<?>>() {{
+ add(PersonResource.class);
+ }};
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ return APP_CLASSES;
+ }
+
+}
diff --git a/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/Person.java b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/Person.java
new file mode 100644
index 0000000..ac17889
--- /dev/null
+++ b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/Person.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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.kryo;
+
+import java.util.Objects;
+
+/**
+ * Test data bean.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+public class Person {
+
+ public String name;
+ public int age;
+ public String address;
+
+ public Person(String name, int age, String address) {
+ this.name = name;
+ this.age = age;
+ this.address = address;
+ }
+
+ public Person() {
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 19 * hash + Objects.hashCode(this.name);
+ hash = 19 * hash + this.age;
+ hash = 19 * hash + Objects.hashCode(this.address);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Person other = (Person) obj;
+ if (!Objects.equals(this.name, other.name)) {
+ return false;
+ }
+ if (this.age != other.age) {
+ return false;
+ }
+ return (Objects.equals(this.address, other.address));
+ }
+}
diff --git a/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/PersonResource.java b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/PersonResource.java
new file mode 100644
index 0000000..58981a8
--- /dev/null
+++ b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/PersonResource.java
@@ -0,0 +1,50 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.kryo;
+
+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;
+
+/**
+ * Test resource.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+@Path("/")
+@Consumes("application/x-kryo")
+@Produces("application/x-kryo")
+public class PersonResource {
+
+ @POST
+ public Person echo(final Person person) {
+ return person;
+ }
+
+ @PUT
+ public void put(final Person person) {
+ }
+
+ @GET
+ public Person get() {
+ return new Person("Wolfgang", 21, "Salzburg");
+ }
+
+}
diff --git a/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/PersonResourceTest.java b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/PersonResourceTest.java
new file mode 100644
index 0000000..4d4eaea
--- /dev/null
+++ b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/PersonResourceTest.java
@@ -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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.kryo;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+
+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;
+
+/**
+ * Test for kryo resource.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+public class PersonResourceTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ enable(TestProperties.DUMP_ENTITY);
+ return new JaxRsApplication();
+ }
+
+ @Override
+ protected void configureClient(final ClientConfig config) {
+ }
+
+ @Test
+ public void testGet() {
+ final Person getResponse = target().request().get(Person.class);
+ assertEquals("Wolfgang", getResponse.name);
+ assertEquals(21, getResponse.age);
+ assertEquals("Salzburg", getResponse.address);
+ }
+
+ @Test
+ public void testPost() {
+ final Person[] testData = new Person[] {new Person("Joseph", 23, "Nazareth"), new Person("Mary", 18, "Nazareth")};
+ for (Person original : testData) {
+ final Person postResponse = target().request()
+ .post(Entity.entity(original, "application/x-kryo"), Person.class);
+ assertEquals(original, postResponse);
+ }
+ }
+
+ @Test
+ public void testPut() {
+ final Response putResponse = target().request().put(Entity.entity(new Person("Jules", 12, "Paris"),
+ "application/x-kryo"));
+ assertEquals(204, putResponse.getStatus());
+ }
+
+}
diff --git a/incubator/open-tracing/pom.xml b/incubator/open-tracing/pom.xml
new file mode 100644
index 0000000..f96771e
--- /dev/null
+++ b/incubator/open-tracing/pom.xml
@@ -0,0 +1,95 @@
+<?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 Public License v. 2.0, which is available at
+ http://www.eclipse.org/legal/epl-2.0.
+
+ This Source Code may also be made available under the following Secondary
+ Licenses when the conditions for such availability set forth in the
+ Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ version 2 with the GNU Classpath Exception, which is available at
+ https://www.gnu.org/software/classpath/license.html.
+
+ SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<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.incubator</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.incubator</groupId>
+ <artifactId>jersey-open-tracing</artifactId>
+ <packaging>jar</packaging>
+
+ <name>jersey-open-tracing</name>
+
+ <description>
+ Jersey support for OpenTracing.
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.opentracing</groupId>
+ <artifactId>opentracing-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>io.opentracing</groupId>
+ <artifactId>opentracing-util</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.sun.istack</groupId>
+ <artifactId>maven-istack-commons-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingApplicationEventListener.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingApplicationEventListener.java
new file mode 100644
index 0000000..d155760
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingApplicationEventListener.java
@@ -0,0 +1,272 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ContainerResponseFilter;
+
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.monitoring.ApplicationEvent;
+import org.glassfish.jersey.server.monitoring.ApplicationEventListener;
+import org.glassfish.jersey.server.monitoring.RequestEvent;
+import org.glassfish.jersey.server.monitoring.RequestEventListener;
+
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.Tracer;
+import io.opentracing.propagation.Format;
+import io.opentracing.propagation.TextMapExtractAdapter;
+import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Application event listener responsible for creating and propagating server-side request {@link io.opentracing.Span}.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+class OpenTracingApplicationEventListener implements ApplicationEventListener {
+ private final Tracer globalTracer = GlobalTracer.get();
+ private final OpenTracingFeature.Verbosity verbosity;
+
+ /**
+ * Creates event listener instance with given {@link org.glassfish.jersey.opentracing.OpenTracingFeature.Verbosity}.
+ *
+ * @param verbosity desired verbosity level
+ */
+ public OpenTracingApplicationEventListener(OpenTracingFeature.Verbosity verbosity) {
+ this.verbosity = verbosity;
+ }
+
+ @Override
+ public void onEvent(ApplicationEvent event) {
+ // we don't care about the server lifecycle
+ }
+
+ @Override
+ public RequestEventListener onRequest(RequestEvent requestEvent) {
+ if (requestEvent.getType() == RequestEvent.Type.START) {
+ Span requestSpan = handleRequestStart(requestEvent.getContainerRequest());
+ return new OpenTracingRequestEventListener(requestSpan);
+ }
+ return null;
+ }
+
+ private Span handleRequestStart(ContainerRequest request) {
+
+ final Map<String, String> mappedHeaders = request
+ .getHeaders()
+ .entrySet()
+ .stream()
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ (entry) -> OpenTracingUtils.formatList(entry.getValue())));
+
+ final SpanContext extractedContext =
+ globalTracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapExtractAdapter(mappedHeaders));
+
+ Tracer.SpanBuilder spanBuilder = globalTracer
+ .buildSpan(OpenTracingFeature.DEFAULT_REQUEST_SPAN_NAME)
+ .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
+ .withTag(Tags.HTTP_METHOD.getKey(), request.getMethod())
+ .withTag(Tags.HTTP_URL.getKey(), request.getRequestUri().toASCIIString())
+ .withTag(LocalizationMessages.OPENTRACING_TAG_REQUEST_HEADERS(),
+ OpenTracingUtils.headersAsString(request.getHeaders()))
+ .withTag(LocalizationMessages.OPENTRACING_TAG_HAS_REQUEST_ENTITY(), request.hasEntity());
+
+ if (extractedContext != null) {
+ spanBuilder = spanBuilder.asChildOf(extractedContext);
+ }
+
+ final Span span = spanBuilder.startManual();
+ request.setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, span);
+ span.log(LocalizationMessages.OPENTRACING_LOG_REQUEST_STARTED());
+ return span;
+ }
+
+ class OpenTracingRequestEventListener implements RequestEventListener {
+ private Span requestSpan;
+ private Span resourceSpan = null;
+
+ OpenTracingRequestEventListener(final Span requestSpan) {
+ this.requestSpan = requestSpan;
+ }
+
+ @Override
+ public void onEvent(RequestEvent event) {
+
+ switch (event.getType()) {
+ case MATCHING_START:
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_MATCHING_STARTED());
+ break;
+
+ case LOCATOR_MATCHED:
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_LOCATOR_MATCHED(
+ OpenTracingUtils.formatList(event.getUriInfo().getMatchedResourceLocators())));
+ break;
+
+ case SUBRESOURCE_LOCATED:
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_SUBRESOURCE_LOCATED(
+ OpenTracingUtils.formatList(event.getUriInfo().getLocatorSubResources())));
+ break;
+
+
+ case REQUEST_MATCHED:
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_REQUEST_MATCHED(event.getUriInfo()
+ .getMatchedResourceMethod()
+ .getInvocable()
+ .getDefinitionMethod()));
+ log(LocalizationMessages.OPENTRACING_LOG_REQUEST_FILTERING_STARTED());
+ break;
+
+ case REQUEST_FILTERED:
+ List<ContainerRequestFilter> requestFilters = new ArrayList<>();
+ event.getContainerRequestFilters().forEach(requestFilters::add);
+
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_REQUEST_FILTERING_FINISHED(requestFilters.size()));
+ if (requestFilters.size() > 0) {
+ log(LocalizationMessages.OPENTRACING_LOG_APPLIED_REQUEST_FILTERS(
+ OpenTracingUtils.formatProviders(requestFilters)));
+ }
+ break;
+
+ case RESOURCE_METHOD_START:
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_RESOURCE_METHOD_STARTED(
+ event.getUriInfo().getMatchedResourceMethod().getInvocable().getDefinitionMethod()));
+
+ resourceSpan = globalTracer.buildSpan(OpenTracingFeature.DEFAULT_RESOURCE_SPAN_NAME)
+ .asChildOf(requestSpan)
+ .startManual();
+
+ event.getContainerRequest().setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, resourceSpan);
+ break;
+
+ case RESOURCE_METHOD_FINISHED:
+ log(LocalizationMessages.OPENTRACING_LOG_RESOURCE_METHOD_FINISHED());
+ break;
+
+ case RESP_FILTERS_START:
+ // this is the first event after resource method is guaranteed to have finished, even for asynchronous
+ // processing; resourceSpan will be finished and the span in the context will be switched back to the
+ // resource span before any further tracing can occur.
+ event.getContainerRequest().setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, requestSpan);
+ resourceSpan.finish();
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_RESPONSE_FILTERING_STARTED());
+ break;
+
+ case RESP_FILTERS_FINISHED:
+ List<ContainerResponseFilter> responseFilters = new ArrayList<>();
+ event.getContainerResponseFilters().forEach(responseFilters::add);
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_RESPONSE_FILTERING_FINISHED(responseFilters.size()));
+ if (responseFilters.size() > 0) {
+ log(LocalizationMessages.OPENTRACING_LOG_APPLIED_RESPONSE_FILTERS(
+ OpenTracingUtils.formatProviders(responseFilters)));
+ }
+ break;
+
+ case ON_EXCEPTION:
+ if (resourceSpan != null) {
+ resourceSpan.setTag(Tags.ERROR.getKey(), true);
+ resourceSpan.finish();
+ }
+ requestSpan.setTag(Tags.ERROR.getKey(), true);
+ logError(event.getException());
+ break;
+
+ case EXCEPTION_MAPPER_FOUND:
+ log(LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPER_FOUND(
+ event.getExceptionMapper().getClass().getName()));
+ break;
+
+ case EXCEPTION_MAPPING_FINISHED:
+ log(LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPING_FINISHED()
+ + (event.isResponseSuccessfullyMapped()
+ ? LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPING_SUCCESS()
+ : LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPING_NOEXCEPTION_OR_FAILED()));
+
+ break;
+
+
+ case FINISHED:
+ if (requestSpan != null) {
+ ContainerResponse response = event.getContainerResponse();
+ if (response != null) {
+ int status = response.getStatus();
+ requestSpan
+ .setTag(Tags.HTTP_STATUS.getKey(), status)
+ .setTag(LocalizationMessages.OPENTRACING_TAG_HAS_RESPONSE_ENTITY(), response.hasEntity())
+ .setTag(LocalizationMessages.OPENTRACING_TAG_RESPONSE_LENGTH(), response.getLength());
+
+ if (400 <= status) {
+ requestSpan.setTag(Tags.ERROR.getKey(), true);
+ }
+ }
+ requestSpan.finish();
+ }
+ break;
+ }
+ }
+
+ /**
+ * Adds a {@link OpenTracingFeature.Verbosity#TRACE}-level log entry into the request span.
+ * @param s log message
+ */
+ private void logVerbose(String s) {
+ log(OpenTracingFeature.Verbosity.TRACE, s);
+ }
+
+ /**
+ * Adds a {@link OpenTracingFeature.Verbosity#INFO}-level log entry into the request span.
+ * @param s log message
+ */
+ private void log(String s) {
+ log(OpenTracingFeature.Verbosity.INFO, s);
+ }
+
+ /**
+ * Adds a log entry with given {@link org.glassfish.jersey.opentracing.OpenTracingFeature.Verbosity}-level into the
+ * request span.
+ *
+ * @param level desired verbosity level
+ * @param s log message
+ */
+ private void log(OpenTracingFeature.Verbosity level, String s) {
+ if (level.ordinal() <= verbosity.ordinal()) {
+ requestSpan.log(s);
+ }
+ }
+
+ /**
+ * Adds an error log into the request span.
+ * @param t exception to be logged.
+ */
+ private void logError(final Throwable t) {
+ Map<String, Object> errorMap = new HashMap<>(2);
+ errorMap.put("event", "error");
+ errorMap.put("error.object", t);
+ requestSpan.log(errorMap);
+ }
+ }
+
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientRequestFilter.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientRequestFilter.java
new file mode 100644
index 0000000..7934488
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientRequestFilter.java
@@ -0,0 +1,75 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.MediaType;
+
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.Tracer;
+import io.opentracing.propagation.Format;
+import io.opentracing.propagation.TextMapInjectAdapter;
+import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Client-side request filter, that creates the client request {@code Span}.
+ * <p>
+ * Stores request-related metadata into the {@code Span} as {@code Tags}
+ * and {@link GlobalTracer#inject(SpanContext, Format, Object) injects} it into http headers.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+class OpenTracingClientRequestFilter implements ClientRequestFilter {
+
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ Tracer.SpanBuilder spanBuilder = GlobalTracer.get()
+ .buildSpan(LocalizationMessages.OPENTRACING_SPAN_PREFIX_CLIENT() + requestContext.getMethod())
+ .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
+ .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
+ .withTag(Tags.HTTP_URL.getKey(), requestContext.getUri().toASCIIString())
+ .withTag(Tags.HTTP_METHOD.getKey(), requestContext.getMethod())
+ .withTag(LocalizationMessages.OPENTRACING_TAG_HAS_REQUEST_ENTITY(), requestContext.hasEntity())
+ .withTag(LocalizationMessages.OPENTRACING_TAG_ACCEPTABLE_MEDIA_TYPES(), requestContext.getAcceptableMediaTypes()
+ .stream()
+ .map(MediaType::toString)
+ .collect(Collectors.joining(", ")))
+ .withTag(LocalizationMessages.OPENTRACING_TAG_REQUEST_HEADERS(),
+ OpenTracingUtils.headersAsString(requestContext.getHeaders()));
+
+ // if pre-stored "span" property is found, propagate the stored context
+ final Object property = requestContext.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+ if (property != null && property instanceof SpanContext) {
+ spanBuilder = spanBuilder.asChildOf((SpanContext) property);
+ }
+ Span span = spanBuilder.startManual();
+
+ requestContext.setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, span);
+ Map<String, String> addedHeaders = new HashMap<>();
+ GlobalTracer.get().inject(span.context(), Format.Builtin.HTTP_HEADERS, new TextMapInjectAdapter(addedHeaders));
+ addedHeaders.forEach((key, value) -> requestContext.getHeaders().add(key, value));
+ }
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientResponseFilter.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientResponseFilter.java
new file mode 100644
index 0000000..0dae48f
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientResponseFilter.java
@@ -0,0 +1,49 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.io.IOException;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
+
+import io.opentracing.Span;
+import io.opentracing.tag.Tags;
+
+/**
+ * Retrieves stored span from the {@link ClientRequestContext} and adds response details to the tracing info.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+public class OpenTracingClientResponseFilter implements ClientResponseFilter {
+
+ @Override
+ public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
+ final Object spanProperty = requestContext.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+ if (spanProperty != null && spanProperty instanceof Span) {
+ ((Span) spanProperty)
+ .setTag(Tags.HTTP_STATUS.getKey(), responseContext.getStatus())
+ .setTag(LocalizationMessages.OPENTRACING_TAG_HAS_RESPONSE_ENTITY(), responseContext.hasEntity())
+ .setTag(LocalizationMessages.OPENTRACING_TAG_RESPONSE_LENGTH(), responseContext.getLength())
+ .setTag(LocalizationMessages.OPENTRACING_TAG_RESPONSE_HEADERS(),
+ OpenTracingUtils.headersAsString(responseContext.getHeaders()))
+ .finish();
+ }
+ }
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingFeature.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingFeature.java
new file mode 100644
index 0000000..6afb1f9
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingFeature.java
@@ -0,0 +1,106 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.util.logging.Logger;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.Beta;
+
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * A feature that enables OpenTracing support on server and client.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+@Beta
+public class OpenTracingFeature implements Feature {
+ private static final Logger LOGGER = Logger.getLogger(OpenTracingFeature.class.getName());
+ private final Verbosity verbosity;
+
+ /**
+ * Creates feature instance with default ({@link Verbosity#INFO} verbosity level.
+ */
+ public OpenTracingFeature() {
+ verbosity = Verbosity.INFO;
+ }
+
+ /**
+ * Creates feature instance with given ({@link Verbosity} level.
+ * @param verbosity desired level of logging verbosity
+ */
+ public OpenTracingFeature(Verbosity verbosity) {
+ this.verbosity = verbosity;
+ }
+
+ /**
+ * Stored span's {@link ContainerRequestContext} property key.
+ */
+ public static final String SPAN_CONTEXT_PROPERTY = "span";
+
+ /**
+ * Default resource span name.
+ */
+ public static final String DEFAULT_RESOURCE_SPAN_NAME = "jersey-resource";
+
+ /**
+ * Default child span name.
+ */
+ public static final String DEFAULT_CHILD_SPAN_NAME = "jersey-resource-app";
+
+ /**
+ * Default request "root" span name.
+ */
+ public static final String DEFAULT_REQUEST_SPAN_NAME = "jersey-server";
+
+ @Override
+ public boolean configure(FeatureContext context) {
+ if (!GlobalTracer.isRegistered()) {
+ LOGGER.warning(LocalizationMessages.OPENTRACING_TRACER_NOT_REGISTERED());
+ }
+
+ switch (context.getConfiguration().getRuntimeType()) {
+ case CLIENT:
+ context.register(OpenTracingClientRequestFilter.class).register(OpenTracingClientResponseFilter.class);
+ break;
+ case SERVER:
+ context.register(new OpenTracingApplicationEventListener(verbosity));
+ }
+ return true;
+ }
+
+ /**
+ * OpenTracing Jersey event logging verbosity.
+ */
+ public enum Verbosity {
+ /**
+ * Only logs basic Jersey processing related events.
+ */
+ INFO,
+
+ /**
+ * Logs more fine grained events related to Jersey processing.
+ */
+ TRACE
+ }
+
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingUtils.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingUtils.java
new file mode 100644
index 0000000..4bc3ad0
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingUtils.java
@@ -0,0 +1,123 @@
+/*
+ * 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 Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.MultivaluedMap;
+
+import io.opentracing.Span;
+import io.opentracing.Tracer;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Utility methods for Jersey OpenTracing integration.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+public class OpenTracingUtils {
+
+ private OpenTracingUtils() {
+ }
+
+ /**
+ * Resolve resource-level span.
+ * <p>
+ * If open tracing is enabled and {@link GlobalTracer} is registered, resource-level span should be stored in the
+ * {@link OpenTracingFeature#SPAN_CONTEXT_PROPERTY}. This span is resolved and returned as an {@link Optional}.
+ *
+ * @param context {@link ContainerRequestContext} instance, can be obtained via {@code @Context} injection
+ * @return {@link Optional} of the resolved span, if found; empty optional if not
+ */
+ public static Optional<Span> getRequestSpan(final ContainerRequestContext context) {
+ if (context != null) {
+ final Object spanProperty = context.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+ if (spanProperty != null && spanProperty instanceof Span) {
+ return Optional.of((Span) spanProperty);
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Create and start ad-hoc custom span with the default name as a child span of the request span (if available).
+ *
+ * @param context {@link ContainerRequestContext} instance, can be obtained via {@code @Context} injection
+ * @return If parent span ("request span") instance is stored in the {@code ContainerRequestContext}, new span is created
+ * as a child span of the found span. If no parent span found, new "root" span is created. In both cases, the returned span
+ * is already started. In order to successfully store the tracing, {@link Span#finish()} needs to be invoked explicitly,
+ * after the traced code finishes.
+ */
+ public static Span getRequestChildSpan(final ContainerRequestContext context) {
+ return getRequestChildSpan(context, OpenTracingFeature.DEFAULT_CHILD_SPAN_NAME);
+ }
+
+ /**
+ * Create and start ad-hoc custom span with a custom name as a child span of the request span (if available).
+ *
+ * @param context {@link ContainerRequestContext} instance, can be obtained via {@code @Context} injection
+ * @param spanName name to be used for the created span
+ * @return If parent span ("request span") instance is stored in the {@code ContainerRequestContext}, new span is created
+ * as a child span of the found span. If no parent span found, new "root" span is created. In both cases, the returned span
+ * is already started. In order to successfully store the tracing, {@link Span#finish()} needs to be invoked explicitly,
+ * after the traced code finishes.
+ */
+ public static Span getRequestChildSpan(final ContainerRequestContext context, final String spanName) {
+ Tracer.SpanBuilder spanBuilder = GlobalTracer.get().buildSpan(spanName);
+ if (context != null) {
+ final Object spanProperty = context.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+ if (spanProperty != null && spanProperty instanceof Span) {
+ spanBuilder = spanBuilder.asChildOf((Span) spanProperty);
+ }
+ }
+ return spanBuilder.startManual();
+ }
+
+ /**
+ * Convert request/response headers from {@link MultivaluedMap} into printable form.
+ *
+ * @param headers multi-valued map of request or response headers
+ * @return {@code String} representation, e.g. "[header1=foo]; [header2=bar, baz]"
+ */
+ static String headersAsString(final MultivaluedMap<String, ?> headers) {
+ return headers.entrySet()
+ .stream()
+ .map((entry) -> "["
+ + entry.getKey() + "="
+ + entry.getValue()
+ .stream()
+ .map(Object::toString)
+ .collect(Collectors.joining(", "))
+ + "]")
+ .collect(Collectors.joining("; "));
+ }
+
+ static String formatList(List<?> list) {
+ return list.stream().map(Object::toString).collect(Collectors.joining(", "));
+ }
+
+ static String formatProviders(Iterable<?> providers) {
+ return StreamSupport.stream(providers.spliterator(), false)
+ .map((provider) -> provider.getClass().getName())
+ .collect(Collectors.joining(", "));
+ }
+}
diff --git a/incubator/open-tracing/src/main/resources/org/glassfish/jersey/opentracing/localization.properties b/incubator/open-tracing/src/main/resources/org/glassfish/jersey/opentracing/localization.properties
new file mode 100644
index 0000000..67f8508
--- /dev/null
+++ b/incubator/open-tracing/src/main/resources/org/glassfish/jersey/opentracing/localization.properties
@@ -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 Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+opentracing.tracer.not.registered=OpenTracing feature is enabled, but no Tracer has been registered. Default no-op tracer will \
+ be used.
+opentracing.log.request.started=Request started.
+opentracing.log.matching.started=Resource matching started.
+opentracing.log.request.matched=Request matched, method: {0}
+opentracing.log.locator.matched=Locator matched. Matched locators: {0}
+opentracing.log.subresource.located=Subresource located: {0}
+opentracing.log.request.filtering.started=Request filtering started.
+opentracing.log.request.filtering.finished=Request filtering finished, {0} filter(s) applied.
+opentracing.log.applied.request.filters=Applied request filters: {0}.
+opentracing.log.resource.method.started=Resource method {0} started.
+opentracing.log.response.filtering.started=Response filtering started.
+opentracing.log.resource.method.finished=Resource method finished.
+opentracing.log.response.filtering.finished=Response filtering finished, {0} filter(s) applied.
+opentracing.log.applied.response.filters=Applied reqsponse filters: {0}.
+opentracing.log.exception=Exception while processing the request: {0}.
+opentracing.log.exception.mapper.found=Exception mapper found: {0}.
+opentracing.log.exception.mapping.finished=Exception mapping finished:
+opentracing.log.exception.mapping.success=successfully mapped
+opentracing.log.exception.mapping.noexception.or.failed=no exception or mapping failed.
+opentracing.tag.request.headers=requestHeaders
+opentracing.tag.response.headers=responseHeaders
+opentracing.tag.has.request.entity=hasRequestEntity
+opentracing.tag.has.response.entity=hasResponseEntity
+opentracing.tag.response.length=responseLength
+opentracing.tag.acceptable.media.types=acceptableMediaTypes
+opentracing.span.prefix.client=jersey-client-
+
diff --git a/incubator/pom.xml b/incubator/pom.xml
new file mode 100644
index 0000000..484edcb
--- /dev/null
+++ b/incubator/pom.xml
@@ -0,0 +1,54 @@
+<?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 Public License v. 2.0, which is available at
+ http://www.eclipse.org/legal/epl-2.0.
+
+ This Source Code may also be made available under the following Secondary
+ Licenses when the conditions for such availability set forth in the
+ Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ version 2 with the GNU Classpath Exception, which is available at
+ https://www.gnu.org/software/classpath/license.html.
+
+ SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<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>
+
+ <groupId>org.glassfish.jersey.incubator</groupId>
+ <artifactId>project</artifactId>
+ <packaging>pom</packaging>
+ <name>jersey-incubator</name>
+
+ <description>
+ Jersey incubator for experimental modules that are in a prototyping or research stage,
+ not ready to be released as part of Jersey (yet).
+ </description>
+
+ <modules>
+ <module>declarative-linking</module>
+ <module>gae-integration</module>
+ <!--<module>html-json</module>-->
+ <module>kryo</module>
+ <module>open-tracing</module>
+ </modules>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>