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>