Initial Contribution
Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/ext/bean-validation/pom.xml b/ext/bean-validation/pom.xml
new file mode 100644
index 0000000..aa1b1f6
--- /dev/null
+++ b/ext/bean-validation/pom.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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.ext</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-bean-validation</artifactId>
+ <name>jersey-ext-bean-validation</name>
+
+ <description>
+ Jersey extension module providing support for Bean Validation (JSR-349) API.
+ </description>
+
+ <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>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <!-- Note: When you're changing these properties change them also in bundles/jax-rs-ri/bundle/pom.xml. -->
+ <Export-Package>org.glassfish.jersey.server.validation.*;version=${project.version}</Export-Package>
+ <Import-Package>javax.validation.*;resolution:=optional;version="${range;[==,3);${javax.validation.api.version}}", *</Import-Package>
+ </instructions>
+ <unpackBundle>true</unpackBundle>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</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>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-validator</artifactId>
+ </dependency>
+
+ <!-- java-el related dependencies are in scope "provided" in hibernate-validator -->
+ <dependency>
+ <groupId>javax.el</groupId>
+ <artifactId>javax.el-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.web</groupId>
+ <artifactId>javax.el</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationConfig.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationConfig.java
new file mode 100644
index 0000000..61f8885
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationConfig.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.validation;
+
+import javax.validation.ConstraintValidatorFactory;
+import javax.validation.MessageInterpolator;
+import javax.validation.ParameterNameProvider;
+import javax.validation.TraversableResolver;
+
+/**
+ * Configuration class for Bean Validation provider.
+ *
+ * @author Michal Gajdos
+ */
+public final class ValidationConfig {
+
+ private MessageInterpolator messageInterpolator;
+ private TraversableResolver traversableResolver;
+ private ConstraintValidatorFactory constraintValidatorFactory;
+ private ParameterNameProvider parameterNameProvider;
+
+ /**
+ * Return {@code MessageInterpolator} implementation used for configuration.
+ *
+ * @return instance of {@code MessageInterpolator} or {@code null} if not defined.
+ */
+ public MessageInterpolator getMessageInterpolator() {
+ return messageInterpolator;
+ }
+
+ /**
+ * Return {@code TraversableResolver} implementation used for configuration.
+ *
+ * @return instance of {@code TraversableResolver} or {@code null} if not defined.
+ */
+ public TraversableResolver getTraversableResolver() {
+ return traversableResolver;
+ }
+
+ /**
+ * Return {@code ConstraintValidatorFactory} implementation used for configuration.
+ *
+ * @return instance of {@code ConstraintValidatorFactory} or {@code null} if not defined.
+ */
+ public ConstraintValidatorFactory getConstraintValidatorFactory() {
+ return constraintValidatorFactory;
+ }
+
+ /**
+ * Return {@code ParameterNameProvider} implementation used for configuration.
+ *
+ * @return instance of {@code ParameterNameProvider} or {@code null} if not defined.
+ */
+ public ParameterNameProvider getParameterNameProvider() {
+ return parameterNameProvider;
+ }
+
+ /**
+ * Defines the message interpolator.
+ * If {@code null} is passed, the default message interpolator is used.
+ *
+ * @param messageInterpolator message interpolator implementation.
+ */
+ public ValidationConfig messageInterpolator(final MessageInterpolator messageInterpolator) {
+ this.messageInterpolator = messageInterpolator;
+ return this;
+ }
+
+ /**
+ * Defines the traversable resolver.
+ * If {@code null} is passed, the default traversable resolver is used.
+ *
+ * @param traversableResolver traversable resolver implementation.
+ */
+ public ValidationConfig traversableResolver(final TraversableResolver traversableResolver) {
+ this.traversableResolver = traversableResolver;
+ return this;
+ }
+
+ /**
+ * Defines the constraint validator factory.
+ * If {@code null} is passed, the default constraint validator factory is used.
+ *
+ * @param constraintValidatorFactory constraint factory implementation.
+ */
+ public ValidationConfig constraintValidatorFactory(final ConstraintValidatorFactory constraintValidatorFactory) {
+ this.constraintValidatorFactory = constraintValidatorFactory;
+ return this;
+ }
+
+ /**
+ * Defines the parameter name provider.
+ * If {@code null} is passed, the default parameter name provider is used.
+ *
+ * @param parameterNameProvider parameter name provider implementation.
+ */
+ public ValidationConfig parameterNameProvider(final ParameterNameProvider parameterNameProvider) {
+ this.parameterNameProvider = parameterNameProvider;
+ return this;
+ }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationError.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationError.java
new file mode 100644
index 0000000..cf2f1c4
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationError.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.validation;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * Default validation error entity to be included in {@code Response}.
+ *
+ * @author Michal Gajdos
+ */
+@XmlRootElement
+@SuppressWarnings("UnusedDeclaration")
+public final class ValidationError {
+
+ private String message;
+
+ private String messageTemplate;
+
+ private String path;
+
+ private String invalidValue;
+
+ /**
+ * Create a {@code ValidationError} instance. Constructor for JAXB providers.
+ */
+ public ValidationError() {
+ }
+
+ /**
+ * Create a {@code ValidationError} instance.
+ *
+ * @param message interpolated error message.
+ * @param messageTemplate non-interpolated error message.
+ * @param path property path.
+ * @param invalidValue value that failed to pass constraints.
+ */
+ public ValidationError(final String message, final String messageTemplate, final String path, final String invalidValue) {
+ this.message = message;
+ this.messageTemplate = messageTemplate;
+ this.path = path;
+ this.invalidValue = invalidValue;
+ }
+
+ /**
+ * Return the interpolated error message for this validation error.
+ *
+ * @return the interpolated error message for this validation error.
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Return the interpolated error message for this validation error.
+ *
+ * @param message the interpolated error message for this validation error.
+ */
+ public void setMessage(final String message) {
+ this.message = message;
+ }
+
+ /**
+ * Return the string representation of the property path to the value.
+ *
+ * @return the string representation of the property path to the value.
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * Set the string representation of the property path to the value.
+ *
+ * @param path the string representation of the property path to the value.
+ */
+ public void setPath(final String path) {
+ this.path = path;
+ }
+
+ /**
+ * Returns the string representation of the value failing to pass the constraint.
+ *
+ * @return the value failing to pass the constraint.
+ */
+ public String getInvalidValue() {
+ return invalidValue;
+ }
+
+ /**
+ * Set the value failing to pass the constraint.
+ *
+ * @param invalidValue the value failing to pass the constraint.
+ */
+ public void setInvalidValue(final String invalidValue) {
+ this.invalidValue = invalidValue;
+ }
+
+ /**
+ * Return the non-interpolated error message for this validation error.
+ *
+ * @return the non-interpolated error message for this validation error.
+ */
+ public String getMessageTemplate() {
+ return messageTemplate;
+ }
+
+ /**
+ * Set the non-interpolated error message for this validation error.
+ *
+ * @param messageTemplate the non-interpolated error message for this validation error.
+ */
+ public void setMessageTemplate(final String messageTemplate) {
+ this.messageTemplate = messageTemplate;
+ }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationFeature.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationFeature.java
new file mode 100644
index 0000000..a012756
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationFeature.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.validation;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.validation.internal.ValidationBinder;
+
+/**
+ * {@code ValidationFeature} used to add Bean Validation (JSR-349) support to the server.
+ *
+ * @author Michal Gajdos
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public final class ValidationFeature implements Feature {
+
+ @Override
+ public boolean configure(final FeatureContext context) {
+ final Configuration config = context.getConfiguration();
+
+ // Validation disabled?
+ if (PropertiesHelper.isProperty(config.getProperty(ServerProperties.BV_FEATURE_DISABLE))) {
+ return false;
+ }
+
+ context.register(new ValidationBinder());
+
+ // Set ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR to make sure no sendError is called on servlet container
+ // when ServerProperties.BV_SEND_ERROR_IN_RESPONSE is enabled.
+ if (PropertiesHelper.isProperty(config.getProperty(ServerProperties.BV_SEND_ERROR_IN_RESPONSE))
+ && config.getProperty(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR) == null) {
+ context.property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true);
+ }
+
+ return true;
+ }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/DefaultConfiguredValidator.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/DefaultConfiguredValidator.java
new file mode 100644
index 0000000..74e1845
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/DefaultConfiguredValidator.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.validation.internal;
+
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.core.Response;
+
+import javax.validation.Configuration;
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.validation.Validator;
+import javax.validation.executable.ExecutableType;
+import javax.validation.executable.ExecutableValidator;
+import javax.validation.metadata.BeanDescriptor;
+import javax.validation.metadata.MethodDescriptor;
+
+import org.glassfish.jersey.server.internal.inject.ConfiguredValidator;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.spi.ValidationInterceptor;
+import org.glassfish.jersey.server.spi.ValidationInterceptorContext;
+
+/**
+ * Default {@link ConfiguredValidator} implementation - delegates calls to the underlying {@link Validator}.
+ *
+ * @author Michal Gajdos
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+class DefaultConfiguredValidator implements ConfiguredValidator, ValidationInterceptor {
+
+ private final Validator delegate;
+ private final Configuration configuration;
+ private final ValidateOnExecutionHandler validateOnExecutionHandler;
+ private final List<ValidationInterceptor> interceptors;
+
+ /**
+ * Create a configured validator instance.
+ *
+ * @param delegate validator to delegate calls to.
+ * @param configuration configuration to obtain {@link ExecutableType executable types} configured in descriptor
+ * from.
+ * @param validateOnExecutionHandler handler for processing {@link javax.validation.executable.ValidateOnExecution}
+ * annotations.
+ * @param interceptors custom validation interceptors.
+ */
+ DefaultConfiguredValidator(final Validator delegate, final Configuration configuration,
+ final ValidateOnExecutionHandler validateOnExecutionHandler,
+ final Iterable<ValidationInterceptor> interceptors) {
+ this.delegate = delegate;
+ this.configuration = configuration;
+ this.validateOnExecutionHandler = validateOnExecutionHandler;
+ this.interceptors = createInterceptorList(interceptors);
+ }
+
+ private List<ValidationInterceptor> createInterceptorList(Iterable<ValidationInterceptor> interceptors) {
+ List<ValidationInterceptor> result = new LinkedList<>();
+ for (ValidationInterceptor i : interceptors) {
+ result.add(i);
+ }
+ result.add(this);
+ return result;
+ }
+
+ @Override
+ public <T> Set<ConstraintViolation<T>> validate(final T object, final Class<?>... groups) {
+ return delegate.validate(object, groups);
+ }
+
+ @Override
+ public <T> Set<ConstraintViolation<T>> validateProperty(final T object, final String propertyName, final Class<?>... groups) {
+ return delegate.validateProperty(object, propertyName, groups);
+ }
+
+ @Override
+ public <T> Set<ConstraintViolation<T>> validateValue(final Class<T> beanType, final String propertyName,
+ final Object value, final Class<?>... groups) {
+ return delegate.validateValue(beanType, propertyName, value, groups);
+ }
+
+ @Override
+ public BeanDescriptor getConstraintsForClass(final Class<?> clazz) {
+ return delegate.getConstraintsForClass(clazz);
+ }
+
+ @Override
+ public <T> T unwrap(final Class<T> type) {
+ return delegate.unwrap(type);
+ }
+
+ @Override
+ public ExecutableValidator forExecutables() {
+ return delegate.forExecutables();
+ }
+
+ @Override
+ public void validateResourceAndInputParams(final Object resource, final Invocable resourceMethod, final Object[] args) {
+
+ ValidationInterceptorExecutor validationExecutor = new ValidationInterceptorExecutor(
+ resource,
+ resourceMethod,
+ args,
+ interceptors.iterator());
+
+ validationExecutor.proceed();
+ }
+
+ // Invoked as the last validation interceptor method in the chain.
+ @Override
+ public void onValidate(final ValidationInterceptorContext ctx) {
+
+ final Object resource = ctx.getResource();
+ final Invocable resourceMethod = ctx.getInvocable();
+ final Object[] args = ctx.getArgs();
+
+ final Set<ConstraintViolation<Object>> constraintViolations = new HashSet<>();
+ final BeanDescriptor beanDescriptor = getConstraintsForClass(resource.getClass());
+
+ // Resource validation.
+ if (beanDescriptor.isBeanConstrained()) {
+ constraintViolations.addAll(validate(resource));
+ }
+
+ if (resourceMethod != null
+ && configuration.getBootstrapConfiguration().isExecutableValidationEnabled()) {
+ final Method handlingMethod = resourceMethod.getHandlingMethod();
+
+ // Resource method validation - input parameters.
+ final MethodDescriptor methodDescriptor = beanDescriptor.getConstraintsForMethod(handlingMethod.getName(),
+ handlingMethod.getParameterTypes());
+
+ if (methodDescriptor != null
+ && methodDescriptor.hasConstrainedParameters()
+ && validateOnExecutionHandler.validateMethod(resource.getClass(),
+ resourceMethod.getDefinitionMethod(),
+ resourceMethod.getHandlingMethod())) {
+ constraintViolations.addAll(forExecutables().validateParameters(resource, handlingMethod, args));
+ }
+ }
+
+ if (!constraintViolations.isEmpty()) {
+ throw new ConstraintViolationException(constraintViolations);
+ }
+ }
+
+ @Override
+ public void validateResult(final Object resource, final Invocable resourceMethod, final Object result) {
+ if (configuration.getBootstrapConfiguration().isExecutableValidationEnabled()) {
+ final Set<ConstraintViolation<Object>> constraintViolations = new HashSet<>();
+ final Method handlingMethod = resourceMethod.getHandlingMethod();
+
+ final BeanDescriptor beanDescriptor = getConstraintsForClass(resource.getClass());
+ final MethodDescriptor methodDescriptor = beanDescriptor.getConstraintsForMethod(handlingMethod.getName(),
+ handlingMethod.getParameterTypes());
+
+ final Method definitionMethod = resourceMethod.getDefinitionMethod();
+
+ if (methodDescriptor != null
+ && methodDescriptor.hasConstrainedReturnValue()
+ && validateOnExecutionHandler.validateMethod(resource.getClass(), definitionMethod, handlingMethod)) {
+ constraintViolations.addAll(forExecutables().validateReturnValue(resource, handlingMethod, result));
+
+ if (result instanceof Response) {
+ constraintViolations.addAll(forExecutables().validateReturnValue(resource, handlingMethod,
+ ((Response) result).getEntity()));
+ }
+ }
+
+ if (!constraintViolations.isEmpty()) {
+ throw new ConstraintViolationException(constraintViolations);
+ }
+ }
+ }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/InjectingConstraintValidatorFactory.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/InjectingConstraintValidatorFactory.java
new file mode 100644
index 0000000..69f2f8c
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/InjectingConstraintValidatorFactory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.validation.internal;
+
+import javax.ws.rs.container.ResourceContext;
+import javax.ws.rs.core.Context;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorFactory;
+
+/**
+ * {@link ConstraintValidatorFactory} with support of injecting Jersey providers/resources.
+ *
+ * @author Michal Gajdos
+ */
+public class InjectingConstraintValidatorFactory implements ConstraintValidatorFactory {
+
+ @Context
+ private ResourceContext resourceContext;
+
+ @Override
+ public <T extends ConstraintValidator<?, ?>> T getInstance(final Class<T> key) {
+ return resourceContext.getResource(key);
+ }
+
+ @Override
+ public void releaseInstance(final ConstraintValidator<?, ?> instance) {
+ // NOOP
+ }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidateOnExecutionHandler.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidateOnExecutionHandler.java
new file mode 100644
index 0000000..2d14937
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidateOnExecutionHandler.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2013, 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.validation.internal;
+
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+
+import javax.validation.Configuration;
+import javax.validation.ValidationException;
+import javax.validation.executable.ExecutableType;
+import javax.validation.executable.ValidateOnExecution;
+
+import org.glassfish.jersey.internal.guava.Multimap;
+import org.glassfish.jersey.internal.guava.Multimaps;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Handler providing methods to determine whether an executable should be validated during the validation process based on the
+ * presence of {@link ValidateOnExecution} annotation.
+ *
+ * @author Michal Gajdos
+ */
+class ValidateOnExecutionHandler {
+
+ private final ConcurrentMap<Method, Boolean> validateMethodCache = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Method, Boolean> validateGetterCache = new ConcurrentHashMap<>();
+
+ private final Configuration config;
+ private final boolean checkOverrides;
+
+ /**
+ * Create {@link ValidateOnExecutionHandler}.
+ *
+ * @param config validation configuration to obtain bootstrap config.
+ * @param checkOverrides flag whether overriding/implementing methods should be checked if the {@link ValidateOnExecution}
+ * annotation is present.
+ */
+ ValidateOnExecutionHandler(final Configuration config, final boolean checkOverrides) {
+ this.config = config;
+ this.checkOverrides = checkOverrides;
+ }
+
+ /**
+ * Determine whether the given {@link Method getter} on the given {@link Class clazz} should be validated during the
+ * resource class validation. See {@code #validateMethod} to understand the difference between this and {@code
+ * #validateMethod}.
+ *
+ * @param clazz class on which the getter will be invoked.
+ * @param method method to be examined.
+ * @return {@code true} if the getter should be validated, {@code false} otherwise.
+ */
+ boolean validateGetter(final Class<?> clazz, final Method method) {
+ if (!validateGetterCache.containsKey(method)) {
+ processMethod(clazz, method, method, true);
+ }
+ return validateGetterCache.get(method);
+ }
+
+ /**
+ * Determine whether the given resource {@link Method method} to-be-executed on the given {@link Class clazz} should be
+ * validated. The difference between this and {@code #validateGetter} method is that this method returns {@code true} if the
+ * {@code method} is getter and validating getter method is not explicitly disabled by {@link ValidateOnExecution} annotation
+ * in the class hierarchy.
+ *
+ * @param clazz class on which the method will be invoked.
+ * @param method method to be examined.
+ * @param validationMethod method used for cache.
+ * @return {@code true} if the method should be validated, {@code false} otherwise.
+ */
+ boolean validateMethod(final Class<?> clazz, final Method method, final Method validationMethod) {
+ if (!validateMethodCache.containsKey(validationMethod)) {
+ processMethod(clazz, method, validationMethod, false);
+ }
+ return validateMethodCache.get(validationMethod);
+ }
+
+ /**
+ * Process the given {@code method} and {@code validationMethod} on given {@code clazz} and determine whether this method
+ * should be validated or not.
+ *
+ * @param clazz class on which the method will be invoked.
+ * @param method method to be examined.
+ * @param validationMethod method used for cache.
+ * @param forceValidation forces validation of a getter if no {@link ValidateOnExecution} annotation is present.
+ */
+ private void processMethod(final Class<?> clazz, final Method method, final Method validationMethod,
+ final boolean forceValidation) {
+ final Deque<Class<?>> hierarchy = getValidationClassHierarchy(clazz);
+ Boolean validateMethod = processAnnotation(method, hierarchy, checkOverrides);
+
+ if (validateMethod != null) {
+ validateMethodCache.putIfAbsent(validationMethod, validateMethod);
+ validateGetterCache.putIfAbsent(validationMethod, validateMethod);
+ }
+
+ // Return value from validation.xml.
+ if (!validateMethodCache.containsKey(validationMethod)) {
+ final Set<ExecutableType> defaultValidatedExecutableTypes = config.getBootstrapConfiguration()
+ .getDefaultValidatedExecutableTypes();
+ validateMethod = validateMethod(method, false, defaultValidatedExecutableTypes);
+
+ validateGetterCache.putIfAbsent(validationMethod, validateMethod || forceValidation);
+
+ // When validateMethod is called and no ValidateOnExecution annotation is present we want to validate getter resource
+ // methods by default (see SPEC).
+ validateMethodCache.putIfAbsent(validationMethod, ReflectionHelper.isGetter(validationMethod) || validateMethod);
+ }
+ }
+
+ /**
+ * Process {@link ValidateOnExecution} annotation for given method on a class hierarchy.
+ *
+ * @param method method to be examined.
+ * @param hierarchy class hierarchy to be examined.
+ * @param checkOverrides flag whether a overriding/implementing methods should also be checked.
+ * @return {@code true} if the method should be validated, {@code false} if not, {@code null} if the flag cannot be
+ * determined (no annotation present).
+ */
+ private Boolean processAnnotation(final Method method, final Deque<Class<?>> hierarchy, final boolean checkOverrides) {
+ // Overridden methods.
+ while (!hierarchy.isEmpty()) {
+ final Class<?> overriddenClass = hierarchy.removeFirst();
+ final Method overriddenMethod =
+ AccessController.doPrivileged(ReflectionHelper.findMethodOnClassPA(overriddenClass, method));
+
+ if (overriddenMethod != null) {
+ // Method.
+ Set<ExecutableType> executableTypes = getExecutableTypes(overriddenMethod);
+ if (!executableTypes.isEmpty()) {
+
+ // If an overriding/implementing method is annotated with @ValidateOnExecution, throw an exception.
+ if (checkOverrides
+ && processAnnotation(method, hierarchy, false) != null) {
+ final String methodName = method.getDeclaringClass().getName() + "#" + method.getName();
+ throw new ValidationException(LocalizationMessages.OVERRIDE_CHECK_ERROR(methodName));
+ }
+
+ return validateMethod(overriddenMethod, true, executableTypes);
+ }
+
+ // Class.
+ executableTypes = getExecutableTypes(overriddenClass);
+ if (!executableTypes.isEmpty()
+ // It should contain not only ExecutableType#IMPLICIT but something else as well.
+ // ExecutableType#IMPLICIT on class itself does nothing.
+ && !(executableTypes.size() == 1 && executableTypes.contains(ExecutableType.IMPLICIT))) {
+
+ return validateMethod(overriddenMethod, false, executableTypes);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Determine whether the given {@link Method method} should be validated depending on the given {@code executableTypes}.
+ *
+ * @param method method to be examined.
+ * @param allowImplicit allows check for {@link ExecutableType#IMPLICIT} type.
+ * @param executableTypes executable types assigned to the method.
+ * @return {@code true} if the method should be validated, {@code false otherwise}.
+ */
+ private boolean validateMethod(final Method method, final boolean allowImplicit, final Set<ExecutableType> executableTypes) {
+ if (executableTypes.contains(ExecutableType.ALL)
+ || (allowImplicit && executableTypes.contains(ExecutableType.IMPLICIT))) {
+ return true;
+ }
+ return ReflectionHelper.isGetter(method) ? executableTypes.contains(ExecutableType.GETTER_METHODS)
+ : executableTypes.contains(ExecutableType.NON_GETTER_METHODS);
+ }
+
+ /**
+ * Return a set of executable types contained in {@link ValidateOnExecution} annotation belonging to the {@code element}.
+ *
+ * @param element element to be examined for {@link ValidateOnExecution}.
+ * @return set of executable types or an empty set if the element is not annotated with {@link ValidateOnExecution}.
+ */
+ private Set<ExecutableType> getExecutableTypes(final AnnotatedElement element) {
+ final ValidateOnExecution validateExecutable = element.getAnnotation(ValidateOnExecution.class);
+ return validateExecutable != null
+ ? Arrays.stream(validateExecutable.type()).collect(Collectors.toSet())
+ : Collections.emptySet();
+ }
+
+ /**
+ * Get a class hierarchy for the given {@code clazz} suitable to be looked for {@link ValidateOnExecution} annotation
+ * in order according to the priority defined by Bean Validation spec (superclasses, interfaces).
+ *
+ * @param clazz class to obtain hierarchy for.
+ * @return class hierarchy.
+ */
+ private Deque<Class<?>> getValidationClassHierarchy(final Class<?> clazz) {
+ final List<Class<?>> hierarchy = new ArrayList<>();
+
+ // Get all superclasses.
+ for (Class<?> currentClass = clazz; currentClass != Object.class; currentClass = currentClass.getSuperclass()) {
+ hierarchy.add(clazz);
+ }
+
+ hierarchy.addAll(getAllValidationInterfaces(clazz));
+ Collections.reverse(hierarchy);
+
+ return new ArrayDeque<>(hierarchy);
+ }
+
+ private List<Class<?>> getAllValidationInterfaces(final Class<?> clazz) {
+ final Multimap<Integer, Class<?>> map = Multimaps.newListMultimap(
+ new TreeMap<Integer, Collection<Class<?>>>(), ArrayList::new);
+
+ retrieveAllValidationInterfaces(clazz, map);
+
+ final List<Class<?>> interfaces = new ArrayList<>(map.values());
+ Collections.reverse(interfaces);
+
+ return interfaces;
+ }
+
+ private int retrieveAllValidationInterfaces(Class<?> clazz, final Multimap<Integer, Class<?>> map) {
+ if (clazz == null) {
+ return 0;
+ }
+
+ int minDepth = 0;
+
+ while (clazz != null) {
+ for (final Class<?> iface : clazz.getInterfaces()) {
+ int depth = retrieveAllValidationInterfaces(iface, map);
+
+ if (!map.containsValue(iface)) {
+ map.put(depth, iface);
+ }
+ minDepth = minDepth > depth ? depth : minDepth;
+ }
+
+ clazz = clazz.getSuperclass();
+ }
+
+ return minDepth + 1;
+ }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidateOnExecutionTraversableResolver.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidateOnExecutionTraversableResolver.java
new file mode 100644
index 0000000..50a1321
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidateOnExecutionTraversableResolver.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2013, 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.validation.internal;
+
+import java.lang.annotation.ElementType;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.validation.Path;
+import javax.validation.TraversableResolver;
+
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * {@link TraversableResolver Traversable resolver} used for handling {@link javax.validation.executable.ValidateOnExecution}
+ * annotations present on property getters when validating resource class.
+ *
+ * @author Michal Gajdos
+ */
+class ValidateOnExecutionTraversableResolver implements TraversableResolver {
+
+ private final TraversableResolver delegate;
+
+ private final ConcurrentMap<String, Method> propertyToMethod = new ConcurrentHashMap<>();
+
+ private final ValidateOnExecutionHandler validateOnExecutionHandler;
+
+ private final boolean validateExecutable;
+
+ /**
+ * Create a new {@link ValidateOnExecutionTraversableResolver}.
+ *
+ * @param delegate delegate requests to this underlying traversable resolver if this one cannot resolve it.
+ * @param validateOnExecutionHandler handler to determine whether a getter should be validated or not.
+ * @param validateExecutable bootstrap flag to enable/disable global validation of executables.
+ */
+ public ValidateOnExecutionTraversableResolver(final TraversableResolver delegate,
+ final ValidateOnExecutionHandler validateOnExecutionHandler,
+ final boolean validateExecutable) {
+ this.delegate = delegate;
+ this.validateExecutable = validateExecutable;
+ this.validateOnExecutionHandler = validateOnExecutionHandler;
+ }
+
+ @Override
+ public boolean isReachable(final Object traversableObject,
+ final Path.Node traversableProperty,
+ final Class<?> rootBeanType,
+ final Path pathToTraversableObject,
+ final ElementType elementType) {
+ // Make sure only getters on entities are validated (not getters on resource classes).
+ final Class<?> traversableObjectClass = traversableObject.getClass();
+ final boolean isEntity = !rootBeanType.equals(traversableObjectClass);
+
+ if (isEntity && validateExecutable && ElementType.METHOD.equals(elementType)) {
+ final String propertyName = traversableProperty.getName();
+ final String propertyKey = traversableObjectClass.getName() + "#" + propertyName;
+
+ if (!propertyToMethod.containsKey(propertyKey)) {
+ final Method getter = getGetterMethod(traversableObjectClass, propertyName);
+
+ if (getter != null) {
+ propertyToMethod.putIfAbsent(propertyKey, getter);
+ }
+ }
+
+ final Method getter = propertyToMethod.get(propertyKey);
+ return getter != null && validateOnExecutionHandler.validateGetter(traversableObjectClass, getter);
+ }
+
+ return delegate.isReachable(traversableObject, traversableProperty, rootBeanType, pathToTraversableObject, elementType);
+ }
+
+ @Override
+ public boolean isCascadable(final Object traversableObject,
+ final Path.Node traversableProperty,
+ final Class<?> rootBeanType,
+ final Path pathToTraversableObject,
+ final ElementType elementType) {
+ return delegate.isCascadable(traversableObject, traversableProperty, rootBeanType, pathToTraversableObject, elementType);
+ }
+
+ /**
+ * Return getter method defined on {@code clazz} of property with given {@code propertyName}.
+ *
+ * @param clazz class to find a getter method on.
+ * @param propertyName name of the property to find a getter for.
+ * @return getter method or {@code null} if the method cannot be found.
+ */
+ private Method getGetterMethod(final Class<?> clazz, final String propertyName) {
+ // Property type.
+ Class<?> propertyType = null;
+ for (final Field field : AccessController.doPrivileged(ReflectionHelper.getAllFieldsPA(clazz))) {
+ if (field.getName().equals(propertyName)) {
+ propertyType = field.getType();
+ }
+ }
+
+ final char[] chars = propertyName.toCharArray();
+ chars[0] = Character.toUpperCase(chars[0]);
+ final String getterPropertyName = new String(chars);
+
+ final String isGetter = "is" + getterPropertyName;
+ final String getGetter = "get" + getterPropertyName;
+
+ for (final Method method : AccessController.doPrivileged(ReflectionHelper.getMethodsPA(clazz))) {
+ final String methodName = method.getName();
+
+ if ((methodName.equals(isGetter) || methodName.equals(getGetter))
+ && ReflectionHelper.isGetter(method)
+ && (propertyType == null || propertyType.isAssignableFrom(method.getReturnType()))) {
+ return AccessController.doPrivileged(ReflectionHelper.findMethodOnClassPA(clazz, method));
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationAutoDiscoverable.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationAutoDiscoverable.java
new file mode 100644
index 0000000..ab373fc
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationAutoDiscoverable.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2013, 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.validation.internal;
+
+import javax.annotation.Priority;
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.internal.spi.AutoDiscoverable;
+import org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable;
+import org.glassfish.jersey.server.validation.ValidationFeature;
+
+/**
+ * {@link AutoDiscoverable} registering {@link ValidationFeature} if this feature is not already registered.
+ *
+ * @author Michal Gajdos
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+@Priority(AutoDiscoverable.DEFAULT_PRIORITY)
+public final class ValidationAutoDiscoverable implements ForcedAutoDiscoverable {
+
+ @Override
+ public void configure(final FeatureContext context) {
+ if (!context.getConfiguration().isRegistered(ValidationFeature.class)) {
+ context.register(ValidationFeature.class);
+ }
+ }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationBinder.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationBinder.java
new file mode 100644
index 0000000..050f920
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationBinder.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.validation.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.WeakHashMap;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.container.ResourceContext;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.ContextResolver;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Providers;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.validation.Configuration;
+import javax.validation.TraversableResolver;
+import javax.validation.Validation;
+import javax.validation.ValidationException;
+import javax.validation.ValidationProviderResolver;
+import javax.validation.Validator;
+import javax.validation.ValidatorContext;
+import javax.validation.ValidatorFactory;
+import javax.validation.spi.ValidationProvider;
+
+import org.glassfish.jersey.internal.ServiceFinder;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.model.internal.RankedComparator;
+import org.glassfish.jersey.model.internal.RankedProvider;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.internal.inject.ConfiguredValidator;
+import org.glassfish.jersey.server.spi.ValidationInterceptor;
+import org.glassfish.jersey.server.validation.ValidationConfig;
+
+/**
+ * Bean Validation provider injection binder.
+ *
+ * @author Michal Gajdos
+ */
+public final class ValidationBinder extends AbstractBinder {
+
+ private static final Logger LOGGER = Logger.getLogger(ValidationBinder.class.getName());
+
+ @Override
+ protected void configure() {
+ bindFactory(DefaultConfigurationProvider.class, Singleton.class).to(Configuration.class).in(Singleton.class);
+
+ bindFactory(DefaultValidatorFactoryProvider.class, Singleton.class).to(ValidatorFactory.class).in(Singleton.class);
+ bindFactory(DefaultValidatorProvider.class, Singleton.class).to(Validator.class).in(Singleton.class);
+
+ bindFactory(ConfiguredValidatorProvider.class, Singleton.class).to(ConfiguredValidator.class);
+
+ // Custom Exception Mapper and Writer - registering in binder to make possible for users register their own providers.
+ bind(ValidationExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class);
+ bind(ValidationErrorMessageBodyWriter.class).to(MessageBodyWriter.class).in(Singleton.class);
+ }
+
+ /**
+ * Factory providing default {@link javax.validation.Configuration} instance.
+ */
+ private static class DefaultConfigurationProvider implements Supplier<Configuration> {
+
+ private final boolean inOsgi;
+
+ public DefaultConfigurationProvider() {
+ this.inOsgi = ReflectionHelper.getOsgiRegistryInstance() != null;
+ }
+
+ @Override
+ public Configuration get() {
+ try {
+ if (!inOsgi) {
+ return Validation.byDefaultProvider().configure();
+ } else {
+ return Validation
+ .byDefaultProvider()
+ .providerResolver(new ValidationProviderResolver() {
+ @Override
+ public List<ValidationProvider<?>> getValidationProviders() {
+ final List<ValidationProvider<?>> validationProviders = new ArrayList<>();
+
+ for (final ValidationProvider validationProvider : ServiceFinder
+ .find(ValidationProvider.class)) {
+ validationProviders.add(validationProvider);
+ }
+
+ return validationProviders;
+ }
+ })
+ .configure();
+ }
+ } catch (final ValidationException e) {
+ // log and re-trow
+ LOGGER.log(Level.FINE, LocalizationMessages.VALIDATION_EXCEPTION_PROVIDER(), e);
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * Factory providing default (un-configured) {@link ValidatorFactory} instance.
+ */
+ private static class DefaultValidatorFactoryProvider implements Supplier<ValidatorFactory> {
+
+ @Inject
+ private Configuration config;
+
+ @Override
+ public ValidatorFactory get() {
+ return config.buildValidatorFactory();
+ }
+ }
+
+ /**
+ * Factory providing default (un-configured) {@link Validator} instance.
+ */
+ private static class DefaultValidatorProvider implements Supplier<Validator> {
+
+ @Inject
+ private ValidatorFactory factory;
+
+ @Override
+ public Validator get() {
+ return factory.getValidator();
+ }
+ }
+
+ /**
+ * Factory providing configured {@link Validator} instance.
+ */
+ private static class ConfiguredValidatorProvider implements Supplier<ConfiguredValidator> {
+
+ @Inject
+ private InjectionManager injectionManager;
+
+ @Inject
+ private Configuration validationConfig;
+ @Inject
+ private ValidatorFactory factory;
+
+ @Context
+ private javax.ws.rs.core.Configuration jaxRsConfig;
+ @Context
+ private Providers providers;
+ @Context
+ private ResourceContext resourceContext;
+
+ private ConfiguredValidator defaultValidator;
+
+ private final WeakHashMap<ContextResolver<ValidationConfig>, ConfiguredValidator> validatorCache =
+ new WeakHashMap<>();
+
+ @Override
+ public ConfiguredValidator get() {
+
+ // Custom Configuration.
+ final ContextResolver<ValidationConfig> contextResolver =
+ providers.getContextResolver(ValidationConfig.class, MediaType.WILDCARD_TYPE);
+
+ if (contextResolver == null) {
+ return getDefaultValidator();
+ } else {
+ if (!validatorCache.containsKey(contextResolver)) {
+ final ValidateOnExecutionHandler validateOnExecutionHandler =
+ new ValidateOnExecutionHandler(validationConfig, !isValidateOnExecutableOverrideCheckDisabled());
+
+ final ValidatorContext context = getDefaultValidatorContext(validateOnExecutionHandler);
+ final ValidationConfig config = contextResolver.getContext(ValidationConfig.class);
+
+ if (config != null) {
+ // MessageInterpolator
+ if (config.getMessageInterpolator() != null) {
+ context.messageInterpolator(config.getMessageInterpolator());
+ }
+
+ // TraversableResolver
+ if (config.getTraversableResolver() != null) {
+ context.traversableResolver(
+ getTraversableResolver(config.getTraversableResolver(), validateOnExecutionHandler));
+ }
+
+ // ConstraintValidatorFactory
+ if (config.getConstraintValidatorFactory() != null) {
+ context.constraintValidatorFactory(config.getConstraintValidatorFactory());
+ }
+
+ // ParameterNameProvider
+ if (config.getParameterNameProvider() != null) {
+ context.parameterNameProvider(config.getParameterNameProvider());
+ }
+ }
+
+ validatorCache.put(contextResolver,
+ new DefaultConfiguredValidator(context.getValidator(), this.validationConfig,
+ validateOnExecutionHandler, getValidationInterceptors()));
+ }
+
+ return validatorCache.get(contextResolver);
+ }
+ }
+
+ private Iterable<ValidationInterceptor> getValidationInterceptors() {
+ final Iterable<RankedProvider<ValidationInterceptor>> validationInterceptorIterable =
+ org.glassfish.jersey.internal.inject.Providers
+ .getAllRankedProviders(injectionManager, ValidationInterceptor.class);
+ return org.glassfish.jersey.internal.inject.Providers.sortRankedProviders(
+ new RankedComparator<ValidationInterceptor>(), validationInterceptorIterable);
+ }
+
+ /**
+ * Return default validator.
+ *
+ * @return default validator.
+ */
+ private ConfiguredValidator getDefaultValidator() {
+ if (defaultValidator == null) {
+ final ValidateOnExecutionHandler validateOnExecutionHandler =
+ new ValidateOnExecutionHandler(validationConfig, !isValidateOnExecutableOverrideCheckDisabled());
+ final Validator validator = getDefaultValidatorContext(validateOnExecutionHandler).getValidator();
+
+ defaultValidator = new DefaultConfiguredValidator(validator, validationConfig,
+ validateOnExecutionHandler, getValidationInterceptors());
+ }
+ return defaultValidator;
+ }
+
+ /**
+ * Return default {@link ValidatorContext validator context} able to inject JAX-RS resources/providers.
+ *
+ * @param handler handler to create traversable resolver for.
+ * @return default validator context.
+ */
+ private ValidatorContext getDefaultValidatorContext(final ValidateOnExecutionHandler handler) {
+ final ValidatorContext context = factory.usingContext();
+
+ // Default Configuration.
+ context.constraintValidatorFactory(resourceContext.getResource(InjectingConstraintValidatorFactory.class));
+
+ // Traversable Resolver.
+ context.traversableResolver(getTraversableResolver(factory.getTraversableResolver(), handler));
+
+ return context;
+ }
+
+ /**
+ * Create traversable resolver able to process {@link javax.validation.executable.ValidateOnExecution} annotation on
+ * beans.
+ *
+ * @param delegate resolver to be wrapped into the custom traversable resolver.
+ * @param handler handler to create traversable resolver for.
+ * @return custom traversable resolver.
+ */
+ private ValidateOnExecutionTraversableResolver getTraversableResolver(TraversableResolver delegate,
+ final ValidateOnExecutionHandler handler) {
+ if (delegate == null) {
+ delegate = validationConfig.getDefaultTraversableResolver();
+ }
+
+ final boolean validationEnabled = validationConfig.getBootstrapConfiguration().isExecutableValidationEnabled();
+ final ValidateOnExecutionTraversableResolver traversableResolver = new
+ ValidateOnExecutionTraversableResolver(delegate, handler, validationEnabled);
+
+ return resourceContext.initResource(traversableResolver);
+ }
+
+ private boolean isValidateOnExecutableOverrideCheckDisabled() {
+ return PropertiesHelper.isProperty(
+ jaxRsConfig.getProperty(ServerProperties.BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK));
+ }
+ }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationErrorMessageBodyWriter.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationErrorMessageBodyWriter.java
new file mode 100644
index 0000000..6383246
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationErrorMessageBodyWriter.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.validation.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+
+import org.glassfish.jersey.message.MessageUtils;
+import org.glassfish.jersey.server.validation.ValidationError;
+
+/**
+ * {@link MessageBodyWriter} providing support for (collections of) {@link ValidationError}
+ * that is able to output instances to {@code text/plain}/{@code text/html}.
+ *
+ * @author Michal Gajdos
+ */
+final class ValidationErrorMessageBodyWriter implements MessageBodyWriter<Object> {
+
+ @Override
+ public boolean isWriteable(final Class<?> type,
+ final Type genericType,
+ final Annotation[] annotations,
+ final MediaType mediaType) {
+ return isSupportedMediaType(mediaType) && isSupportedType(type, genericType);
+ }
+
+ private boolean isSupportedType(final Class<?> type, final Type genericType) {
+ if (ValidationError.class.isAssignableFrom(type)) {
+ return true;
+ } else if (Collection.class.isAssignableFrom(type)) {
+ if (genericType instanceof ParameterizedType) {
+ return ValidationError.class
+ .isAssignableFrom((Class) ((ParameterizedType) genericType).getActualTypeArguments()[0]);
+ }
+ }
+ return false;
+ }
+
+ private boolean isSupportedMediaType(final MediaType mediaType) {
+ return MediaType.TEXT_HTML_TYPE.equals(mediaType) || MediaType.TEXT_PLAIN_TYPE.equals(mediaType);
+ }
+
+ @Override
+ public long getSize(final Object validationErrors,
+ final Class<?> type,
+ final Type genericType,
+ final Annotation[] annotations,
+ final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(final Object entity,
+ final Class<?> type,
+ final Type genericType,
+ final Annotation[] annotations,
+ final MediaType mediaType,
+ final MultivaluedMap<String, Object> httpHeaders,
+ final OutputStream entityStream) throws IOException, WebApplicationException {
+ final Collection<ValidationError> errors;
+
+ if (entity instanceof ValidationError) {
+ errors = Collections.singleton((ValidationError) entity);
+ } else {
+ //noinspection unchecked
+ errors = (Collection<ValidationError>) entity;
+ }
+
+ final boolean isPlain = MediaType.TEXT_PLAIN_TYPE.getSubtype().equals(mediaType.getSubtype());
+
+ final StringBuilder builder = new StringBuilder();
+
+ // Root <div>
+ if (!isPlain) {
+ builder.append("<div class=\"validation-errors\">");
+ }
+
+ for (final ValidationError error : errors) {
+ if (!isPlain) {
+ builder.append("<div class=\"validation-error\">");
+ }
+
+ // Message.
+ builder.append(isPlain ? error.getMessage() : "<span class=\"message\">" + error.getMessage() + "</span>");
+ builder.append(' ');
+
+ builder.append('(');
+
+ // Path.
+ builder.append(isPlain ? "path = " : ("<span class=\"path\"><strong>path</strong> = "));
+ builder.append(isPlain ? error.getPath() : (error.getPath() + "</span>"));
+ builder.append(',');
+ builder.append(' ');
+
+ // Invalid value.
+ builder.append(isPlain ? "invalidValue = " : ("<span class=\"invalid-value\"><strong>invalidValue</strong> = "));
+ builder.append(isPlain ? error.getInvalidValue() : (error.getInvalidValue() + "</span>"));
+
+ builder.append(')');
+
+ if (!isPlain) {
+ builder.append("</div>");
+ } else {
+ builder.append('\n');
+ }
+ }
+
+ // Root <div>
+ if (!isPlain) {
+ builder.append("</div>");
+ }
+
+ entityStream.write(builder.toString().getBytes(MessageUtils.getCharset(mediaType)));
+ entityStream.flush();
+ }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationExceptionMapper.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationExceptionMapper.java
new file mode 100644
index 0000000..b569607
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationExceptionMapper.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.validation.internal;
+
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.GenericEntity;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Variant;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import javax.inject.Provider;
+import javax.validation.ConstraintViolationException;
+import javax.validation.ValidationException;
+
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.validation.ValidationError;
+
+/**
+ * {@link ExceptionMapper} for {@link ValidationException}.
+ * <p/>
+ * If {@value ServerProperties#BV_SEND_ERROR_IN_RESPONSE} property is enabled then a list of {@link ValidationError}
+ * instances is sent in {@link Response} as well (in addition to HTTP 400/500 status code). Supported media types are:
+ * {@code application/json}/{@code application/xml} (in appropriate provider is registered on server) or
+ * {@code text/html}/{@code text/plain} (via custom {@link ValidationErrorMessageBodyWriter}).
+ *
+ * @author Michal Gajdos
+ */
+public final class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {
+
+ private static final Logger LOGGER = Logger.getLogger(ValidationExceptionMapper.class.getName());
+
+ @Context
+ private Configuration config;
+ @Context
+ private Provider<Request> request;
+
+ @Override
+ public Response toResponse(final ValidationException exception) {
+ if (exception instanceof ConstraintViolationException) {
+ LOGGER.log(Level.FINER, LocalizationMessages.CONSTRAINT_VIOLATIONS_ENCOUNTERED(), exception);
+
+ final ConstraintViolationException cve = (ConstraintViolationException) exception;
+ final Response.ResponseBuilder response = Response.status(ValidationHelper.getResponseStatus(cve));
+
+ // Entity.
+ final Object property = config.getProperty(ServerProperties.BV_SEND_ERROR_IN_RESPONSE);
+ if (property != null && Boolean.valueOf(property.toString())) {
+ final List<Variant> variants = Variant.mediaTypes(
+ MediaType.TEXT_PLAIN_TYPE,
+ MediaType.TEXT_HTML_TYPE,
+ MediaType.APPLICATION_XML_TYPE,
+ MediaType.APPLICATION_JSON_TYPE).build();
+ final Variant variant = request.get().selectVariant(variants);
+ if (variant != null) {
+ response.type(variant.getMediaType());
+ } else {
+
+ // default media type which will be used only when none media type from {@value variants} is in accept
+ // header of original request.
+ // could be settable by configuration property.
+ response.type(MediaType.TEXT_PLAIN_TYPE);
+ }
+ response.entity(
+ new GenericEntity<>(
+ ValidationHelper.constraintViolationToValidationErrors(cve),
+ new GenericType<List<ValidationError>>() {}.getType()
+ )
+ );
+ }
+
+ return response.build();
+ } else {
+ LOGGER.log(Level.WARNING, LocalizationMessages.VALIDATION_EXCEPTION_RAISED(), exception);
+
+ return Response.serverError().entity(exception.getMessage()).build();
+ }
+ }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationHelper.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationHelper.java
new file mode 100644
index 0000000..5e92de9
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationHelper.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.server.validation.internal;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.validation.ElementKind;
+import javax.validation.Path;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.validation.ValidationError;
+
+/**
+ * Utility methods for Bean Validation processing.
+ *
+ * @author Michal Gajdos
+ * @since 2.3
+ */
+public final class ValidationHelper {
+
+ /**
+ * Extract {@link ConstraintViolation constraint violations} from given exception and transform them into a list of
+ * {@link ValidationError validation errors}.
+ *
+ * @param violation exception containing constraint violations.
+ * @return list of validation errors (not {@code null}).
+ */
+ public static List<ValidationError> constraintViolationToValidationErrors(final ConstraintViolationException violation) {
+ return violation.getConstraintViolations().stream().map(violation1 -> new ValidationError(
+ violation1.getMessage(),
+ violation1.getMessageTemplate(),
+ getViolationPath(violation1),
+ getViolationInvalidValue(violation1.getInvalidValue())
+ )).collect(Collectors.toList());
+ }
+
+ /**
+ * Provide a string value of (invalid) value that caused the exception.
+ *
+ * @param invalidValue invalid value causing BV exception.
+ * @return string value of given object or {@code null}.
+ */
+ private static String getViolationInvalidValue(final Object invalidValue) {
+ if (invalidValue == null) {
+ return null;
+ }
+
+ if (invalidValue.getClass().isArray()) {
+ if (invalidValue instanceof Object[]) {
+ return Arrays.toString((Object[]) invalidValue);
+ } else if (invalidValue instanceof boolean[]) {
+ return Arrays.toString((boolean[]) invalidValue);
+ } else if (invalidValue instanceof byte[]) {
+ return Arrays.toString((byte[]) invalidValue);
+ } else if (invalidValue instanceof char[]) {
+ return Arrays.toString((char[]) invalidValue);
+ } else if (invalidValue instanceof double[]) {
+ return Arrays.toString((double[]) invalidValue);
+ } else if (invalidValue instanceof float[]) {
+ return Arrays.toString((float[]) invalidValue);
+ } else if (invalidValue instanceof int[]) {
+ return Arrays.toString((int[]) invalidValue);
+ } else if (invalidValue instanceof long[]) {
+ return Arrays.toString((long[]) invalidValue);
+ } else if (invalidValue instanceof short[]) {
+ return Arrays.toString((short[]) invalidValue);
+ }
+ }
+
+ return invalidValue.toString();
+ }
+
+ /**
+ * Get a path to a field causing constraint violations.
+ *
+ * @param violation constraint violation.
+ * @return path to a property that caused constraint violations.
+ */
+ private static String getViolationPath(final ConstraintViolation violation) {
+ final String rootBeanName = violation.getRootBean().getClass().getSimpleName();
+ final String propertyPath = violation.getPropertyPath().toString();
+
+ return rootBeanName + (!"".equals(propertyPath) ? '.' + propertyPath : "");
+ }
+
+ /**
+ * Determine the response status (400 or 500) from the given BV exception.
+ *
+ * @param violation BV exception.
+ * @return response status (400 or 500).
+ */
+ public static Response.Status getResponseStatus(final ConstraintViolationException violation) {
+ final Iterator<ConstraintViolation<?>> iterator = violation.getConstraintViolations().iterator();
+
+ if (iterator.hasNext()) {
+ for (final Path.Node node : iterator.next().getPropertyPath()) {
+ final ElementKind kind = node.getKind();
+
+ if (ElementKind.RETURN_VALUE.equals(kind)) {
+ return Response.Status.INTERNAL_SERVER_ERROR;
+ }
+ }
+ }
+
+ return Response.Status.BAD_REQUEST;
+ }
+
+ /**
+ * Prevent instantiation.
+ */
+ private ValidationHelper() {
+ }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationInterceptorExecutor.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationInterceptorExecutor.java
new file mode 100644
index 0000000..6f0f728
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationInterceptorExecutor.java
@@ -0,0 +1,92 @@
+/*
+ * 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.server.validation.internal;
+
+import java.util.Iterator;
+
+import javax.validation.ValidationException;
+
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.spi.ValidationInterceptor;
+import org.glassfish.jersey.server.spi.ValidationInterceptorContext;
+
+/**
+ * Validation executor for resource method validation processing. It is intended for a one-off usage
+ * when the executor instance serves also as a {@link org.glassfish.jersey.server.spi.ValidationInterceptorContext}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+final class ValidationInterceptorExecutor implements ValidationInterceptorContext {
+
+ private Object resource;
+ private Object[] args;
+ private final Invocable invocable;
+
+ private final Iterator<ValidationInterceptor> iterator;
+
+ /**
+ * Create a one-off validation executor for given resource, invocable and parameter and given
+ * interceptors.
+ *
+ * @param resource actual resource instance to get validated
+ * @param invocable resource method
+ * @param args actual resource method parameters
+ * @param iterator validator interceptors to be involved
+ */
+ public ValidationInterceptorExecutor(
+ final Object resource,
+ final Invocable invocable,
+ final Object[] args,
+ final Iterator<ValidationInterceptor> iterator) {
+
+ this.resource = resource;
+ this.invocable = invocable;
+ this.args = args;
+ this.iterator = iterator;
+ }
+
+ @Override
+ public Object getResource() {
+ return resource;
+ }
+
+ @Override
+ public void setResource(final Object resource) {
+ this.resource = resource;
+ }
+
+ @Override
+ public Invocable getInvocable() {
+ return invocable;
+ }
+
+ @Override
+ public Object[] getArgs() {
+ return args;
+ }
+
+ @Override
+ public void setArgs(final Object[] args) {
+ this.args = args;
+ }
+
+ @Override
+ public void proceed() throws ValidationException {
+ final ValidationInterceptor validationInterceptor = iterator.next();
+ validationInterceptor.onValidate(this);
+ }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/package-info.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/package-info.java
new file mode 100644
index 0000000..ab78478
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/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 server-side bean validation internal classes.
+ */
+package org.glassfish.jersey.server.validation.internal;
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/package-info.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/package-info.java
new file mode 100644
index 0000000..ff31f90
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/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 server-side bean validation classes.
+ */
+package org.glassfish.jersey.server.validation;
diff --git a/ext/bean-validation/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable b/ext/bean-validation/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable
new file mode 100644
index 0000000..1afc0d8
--- /dev/null
+++ b/ext/bean-validation/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable
@@ -0,0 +1 @@
+org.glassfish.jersey.server.validation.internal.ValidationAutoDiscoverable
\ No newline at end of file
diff --git a/ext/bean-validation/src/main/resources/org/glassfish/jersey/server/validation/internal/localization.properties b/ext/bean-validation/src/main/resources/org/glassfish/jersey/server/validation/internal/localization.properties
new file mode 100644
index 0000000..97018fa
--- /dev/null
+++ b/ext/bean-validation/src/main/resources/org/glassfish/jersey/server/validation/internal/localization.properties
@@ -0,0 +1,21 @@
+#
+# Copyright (c) 2013, 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
+#
+
+constraint.violations.encountered=Following ConstraintViolations has been encountered.
+# {0} - Class#methodName, i.e. java.lang.String#intern
+override.check.error=Multiple ValidateOnExecution annotation definitions are hosted in the hierarchy of {0} method.
+validation.exception.raised=Unexpected Bean Validation problem.
+validation.exception.provider=Failed to configure the default validation provider.
diff --git a/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/pom.xml b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/pom.xml
new file mode 100644
index 0000000..8b7598c
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/pom.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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.ext.cdi</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-cdi1x-ban-custom-hk2-binding</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-ext-cdi1x-ban-custom-hk2-binding</name>
+
+ <description>Jersey CDI integration - this module disables custom HK2 bindings</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.cdi</groupId>
+ <artifactId>jersey-cdi1x</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <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>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>org.glassfish.jersey.ext.cdi1x.hk2ban</Export-Package>
+ <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
+ <_nodefaultversion>false</_nodefaultversion>
+ </instructions>
+ </configuration>
+ <executions>
+ <execution>
+ <id>osgi-bundle</id>
+ <phase>package</phase>
+ <goals>
+ <goal>bundle</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/java/org/glassfish/jersey/ext/cdi1x/hk2ban/EmptyHk2CustomInjectionTypeProvider.java b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/java/org/glassfish/jersey/ext/cdi1x/hk2ban/EmptyHk2CustomInjectionTypeProvider.java
new file mode 100644
index 0000000..2e408f0
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/java/org/glassfish/jersey/ext/cdi1x/hk2ban/EmptyHk2CustomInjectionTypeProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.ext.cdi1x.hk2ban;
+
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.Set;
+
+import org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider;
+
+/**
+ * Utility class that effectively disables any attempts of Jersey/CDI integration layer
+ * to delegate injection from CDI to HK2.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public final class EmptyHk2CustomInjectionTypeProvider implements Hk2CustomBoundTypesProvider {
+
+ @Override
+ public Set<Type> getHk2Types() {
+ return Collections.emptySet();
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/java/org/glassfish/jersey/ext/cdi1x/hk2ban/package-info.java b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/java/org/glassfish/jersey/ext/cdi1x/hk2ban/package-info.java
new file mode 100644
index 0000000..ecc230e
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/java/org/glassfish/jersey/ext/cdi1x/hk2ban/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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
+ */
+
+/**
+ * Jersey package supporting Jersey HK2 custom injection binding
+ * to be disabled in CDI environment.
+ */
+package org.glassfish.jersey.ext.cdi1x.hk2ban;
diff --git a/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider
new file mode 100644
index 0000000..5fe4f00
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.hk2ban.EmptyHk2CustomInjectionTypeProvider
diff --git a/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/test/java/org/glassfish/jersey/ext/cdi1x/hk2ban/EmptyHk2CustomInjectionTypeProviderTest.java b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/test/java/org/glassfish/jersey/ext/cdi1x/hk2ban/EmptyHk2CustomInjectionTypeProviderTest.java
new file mode 100644
index 0000000..6bce650
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/test/java/org/glassfish/jersey/ext/cdi1x/hk2ban/EmptyHk2CustomInjectionTypeProviderTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.ext.cdi1x.hk2ban;
+
+import java.lang.reflect.Type;
+
+import org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider;
+import org.glassfish.jersey.internal.ServiceFinder;
+
+import org.junit.Test;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.emptyCollectionOf;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Test for {@link EmptyHk2CustomInjectionTypeProvider}.
+ * Make sure that the empty provider could be loaded and provides an empty type set.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class EmptyHk2CustomInjectionTypeProviderTest {
+
+ /**
+ * Test sub-resource detection.
+ */
+ @Test
+ public void testEmptyProviderLookup() {
+
+ final Hk2CustomBoundTypesProvider[] providers = ServiceFinder.find(Hk2CustomBoundTypesProvider.class).toArray();
+ assertThat(providers, is(notNullValue()));
+ assertThat(providers.length, is(1));
+
+ final Hk2CustomBoundTypesProvider theOnlyProvider = providers[0];
+ assertThat(theOnlyProvider, is(instanceOf(EmptyHk2CustomInjectionTypeProvider.class)));
+ assertThat(theOnlyProvider.getHk2Types(), is(emptyCollectionOf(Type.class)));
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x-servlet/pom.xml b/ext/cdi/jersey-cdi1x-servlet/pom.xml
new file mode 100644
index 0000000..e09cfe5
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/pom.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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.ext.cdi</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-cdi1x-servlet</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-ext-cdi1x-servlet</name>
+
+ <description>Jersey CDI 1.x Servlet Support</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>${servlet3.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.enterprise</groupId>
+ <artifactId>cdi-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.cdi</groupId>
+ <artifactId>jersey-cdi1x</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <!--
+ Excluding only *.tests.* packages for this module. At least one class has to exist in non-excluded
+ packages to generate proper -javadoc.jar file. See https://jira.codehaus.org/browse/MJAVADOC-329
+ -->
+ <excludePackageNames>*.tests.*</excludePackageNames>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <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>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>org.glassfish.jersey.ext.cdi1x.servlet.internal</Export-Package>
+ <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
+ <_nodefaultversion>false</_nodefaultversion>
+ </instructions>
+ </configuration>
+ <executions>
+ <execution>
+ <id>osgi-bundle</id>
+ <phase>package</phase>
+ <goals>
+ <goal>bundle</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/CdiExternalRequestScope.java b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/CdiExternalRequestScope.java
new file mode 100644
index 0000000..94217f2
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/CdiExternalRequestScope.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.ext.cdi1x.servlet.internal;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.glassfish.jersey.ext.cdi1x.internal.JerseyVetoed;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.server.spi.ExternalRequestContext;
+import org.glassfish.jersey.server.spi.ExternalRequestScope;
+
+/**
+ * Weld specific request scope to align CDI request context with Jersey.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@ApplicationScoped
+@JerseyVetoed
+public class CdiExternalRequestScope implements ExternalRequestScope<Object> {
+
+ public static final ThreadLocal<InjectionManager> actualInjectionManager = new ThreadLocal<>();
+
+ @Override
+ public ExternalRequestContext<Object> open(InjectionManager injectionManager) {
+ actualInjectionManager.set(injectionManager);
+ return new ExternalRequestContext<>(null);
+ }
+
+ @Override
+ public void resume(final ExternalRequestContext<Object> ctx, InjectionManager injectionManager) {
+ actualInjectionManager.set(injectionManager);
+ }
+
+ @Override
+ public void suspend(final ExternalRequestContext<Object> ctx, InjectionManager injectionManager) {
+ actualInjectionManager.remove();
+ }
+
+ @Override
+ public void close() {
+ actualInjectionManager.remove();
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/CdiExternalRequestScopeExtension.java b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/CdiExternalRequestScopeExtension.java
new file mode 100644
index 0000000..53fe1a0
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/CdiExternalRequestScopeExtension.java
@@ -0,0 +1,148 @@
+/*
+ * 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.ext.cdi1x.servlet.internal;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.enterprise.context.Dependent;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Any;
+import javax.enterprise.inject.Default;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.InjectionTarget;
+import javax.enterprise.util.AnnotationLiteral;
+
+/**
+ * CDI extension to register {@link CdiExternalRequestScope}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class CdiExternalRequestScopeExtension implements Extension {
+
+ public static final AnnotationLiteral<Default> DEFAULT_ANNOTATION_LITERAL = new AnnotationLiteral<Default>() {};
+ public static final AnnotationLiteral<Any> ANY_ANNOTATION_LITERAL = new AnnotationLiteral<Any>() {};
+
+ private AnnotatedType<CdiExternalRequestScope> requestScopeType;
+
+ /**
+ * Register our external request scope.
+ *
+ * @param beforeBeanDiscoveryEvent CDI bootstrap event.
+ * @param beanManager current bean manager.
+ */
+ private void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscoveryEvent, final BeanManager beanManager) {
+ requestScopeType = beanManager.createAnnotatedType(CdiExternalRequestScope.class);
+ beforeBeanDiscoveryEvent.addAnnotatedType(requestScopeType);
+ }
+
+ /**
+ * Register a CDI bean for the external scope.
+ *
+ * @param afterBeanDiscovery CDI bootstrap event.
+ * @param beanManager current bean manager
+ */
+ private void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
+
+ // we need the injection target so that CDI could instantiate the original interceptor for us
+ final InjectionTarget<CdiExternalRequestScope> interceptorTarget = beanManager.createInjectionTarget(requestScopeType);
+
+
+ afterBeanDiscovery.addBean(new Bean<CdiExternalRequestScope>() {
+
+ @Override
+ public Class<?> getBeanClass() {
+ return CdiExternalRequestScope.class;
+ }
+
+ @Override
+ public Set<InjectionPoint> getInjectionPoints() {
+ return interceptorTarget.getInjectionPoints();
+ }
+
+ @Override
+ public String getName() {
+ return "CdiExternalRequestScope";
+ }
+
+ @Override
+ public Set<Annotation> getQualifiers() {
+ return new HashSet<Annotation>() {{
+ add(DEFAULT_ANNOTATION_LITERAL);
+ add(ANY_ANNOTATION_LITERAL);
+ }};
+ }
+
+ @Override
+ public Class<? extends Annotation> getScope() {
+ return Dependent.class;
+ }
+
+ @Override
+ public Set<Class<? extends Annotation>> getStereotypes() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<Type> getTypes() {
+ return new HashSet<Type>() {{
+ add(CdiExternalRequestScope.class);
+ add(Object.class);
+ }};
+ }
+
+ @Override
+ public boolean isAlternative() {
+ return false;
+ }
+
+ @Override
+ public boolean isNullable() {
+ return false;
+ }
+
+ @Override
+ public CdiExternalRequestScope create(CreationalContext<CdiExternalRequestScope> ctx) {
+
+ final CdiExternalRequestScope result = interceptorTarget.produce(ctx);
+ interceptorTarget.inject(result, ctx);
+ interceptorTarget.postConstruct(result);
+ return result;
+ }
+
+
+ @Override
+ public void destroy(CdiExternalRequestScope instance,
+ CreationalContext<CdiExternalRequestScope> ctx) {
+
+ interceptorTarget.preDestroy(instance);
+ interceptorTarget.dispose(instance);
+ ctx.release();
+ }
+ });
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/ServletInjectionManagerStore.java b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/ServletInjectionManagerStore.java
new file mode 100644
index 0000000..2c6f154
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/ServletInjectionManagerStore.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.ext.cdi1x.servlet.internal;
+
+import org.glassfish.jersey.ext.cdi1x.internal.GenericInjectionManagerStore;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * {@link InjectionManagerStore injection manager} for servlet based containers. The provider
+ * enables WAR and EAR to be deployed on a servlet container and be properly injected.
+ *
+ * @author Michal Gajdos
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @since 2.17
+ */
+public class ServletInjectionManagerStore extends GenericInjectionManagerStore {
+
+ @Override
+ public InjectionManager lookupInjectionManager() {
+ return CdiExternalRequestScope.actualInjectionManager.get();
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/package-info.java b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/package-info.java
new file mode 100644
index 0000000..cad877a
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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
+ */
+
+/**
+ * Jersey internal package supporting Jersey CDI integration in Servlet containers.
+ *
+ * @since 2.17
+ */
+package org.glassfish.jersey.ext.cdi1x.servlet.internal;
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/beans.xml b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000..aaf0547
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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
+
+-->
+
+<beans/>
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
new file mode 100644
index 0000000..952037c
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.servlet.internal.CdiExternalRequestScopeExtension
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore
new file mode 100644
index 0000000..fa979a0
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.servlet.internal.ServletInjectionManagerStore
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ExternalRequestScope b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ExternalRequestScope
new file mode 100644
index 0000000..f46abfc
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ExternalRequestScope
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.servlet.internal.CdiExternalRequestScope
\ No newline at end of file
diff --git a/ext/cdi/jersey-cdi1x-transaction/pom.xml b/ext/cdi/jersey-cdi1x-transaction/pom.xml
new file mode 100644
index 0000000..cea42c1
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/pom.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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.ext.cdi</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-cdi1x-transaction</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-ext-cdi1x-transaction</name>
+
+ <description>Jersey CDI 1.x Transactional Support</description>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>javax</groupId>
+ <artifactId>javaee-api</artifactId>
+ <version>7.0</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.cdi</groupId>
+ <artifactId>jersey-cdi1x</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <!--
+ Excluding only *.tests.* packages for this module. At least one class has to exist in non-excluded
+ packages to generate proper -javadoc.jar file. See https://jira.codehaus.org/browse/MJAVADOC-329
+ -->
+ <excludePackageNames>*.tests.*</excludePackageNames>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <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>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>org.glassfish.jersey.ext.cdi1x.transaction.internal</Export-Package>
+ <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
+ <_nodefaultversion>false</_nodefaultversion>
+ </instructions>
+ </configuration>
+ <executions>
+ <execution>
+ <id>osgi-bundle</id>
+ <phase>package</phase>
+ <goals>
+ <goal>bundle</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/TransactionalExceptionInterceptorProvider.java b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/TransactionalExceptionInterceptorProvider.java
new file mode 100644
index 0000000..54ac7eb
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/TransactionalExceptionInterceptorProvider.java
@@ -0,0 +1,109 @@
+/*
+ * 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.ext.cdi1x.transaction.internal;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import javax.annotation.Priority;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterTypeDiscovery;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.inject.Qualifier;
+import javax.interceptor.Interceptor;
+import javax.transaction.TransactionalException;
+
+import org.glassfish.jersey.ext.cdi1x.internal.CdiUtil;
+import org.glassfish.jersey.ext.cdi1x.internal.GenericCdiBeanSupplier;
+import org.glassfish.jersey.internal.inject.Binding;
+import org.glassfish.jersey.internal.inject.Bindings;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.server.spi.ComponentProvider;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Jersey CDI extension that provides means to retain {@link WebApplicationException}
+ * thrown from JAX-RS components implemented as CDI transactional beans.
+ * This is to avoid the {@link WebApplicationException} from being masked with
+ * {@link TransactionalException}. Jersey will try to restore the original
+ * JAX-RS exception using {@link TransactionalExceptionMapper}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Priority(value = Interceptor.Priority.PLATFORM_BEFORE + 199)
+public class TransactionalExceptionInterceptorProvider implements ComponentProvider, Extension {
+
+ private InjectionManager injectionManager;
+ private BeanManager beanManager;
+
+ @Qualifier
+ @Retention(RUNTIME)
+ @Target({METHOD, FIELD, PARAMETER, TYPE})
+ public static @interface WaeQualifier {
+ }
+
+ @Override
+ public void initialize(final InjectionManager injectionManager) {
+ this.injectionManager = injectionManager;
+ this.beanManager = CdiUtil.getBeanManager();
+ }
+
+ @Override
+ public boolean bind(final Class<?> component, final Set<Class<?>> providerContracts) {
+ return false;
+ }
+
+ @Override
+ public void done() {
+ if (beanManager != null) {
+ bindWaeRestoringExceptionMapper();
+ }
+ }
+
+ private void bindWaeRestoringExceptionMapper() {
+ GenericCdiBeanSupplier beanSupplier =
+ new GenericCdiBeanSupplier(TransactionalExceptionMapper.class, injectionManager, beanManager, true);
+ Binding binding = Bindings.supplier(beanSupplier).to(ExceptionMapper.class);
+ injectionManager.register(binding);
+ }
+
+ @SuppressWarnings("unused")
+ private void afterTypeDiscovery(@Observes final AfterTypeDiscovery afterTypeDiscovery) {
+ final List<Class<?>> interceptors = afterTypeDiscovery.getInterceptors();
+ interceptors.add(WebAppExceptionInterceptor.class);
+ }
+
+ @SuppressWarnings("unused")
+ private void beforeBeanDiscovery(@Observes final BeforeBeanDiscovery beforeBeanDiscovery, final javax.enterprise.inject.spi
+ .BeanManager beanManager) {
+ beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(WebAppExceptionHolder.class));
+ beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(WebAppExceptionInterceptor.class));
+ beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(TransactionalExceptionMapper.class));
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/TransactionalExceptionMapper.java b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/TransactionalExceptionMapper.java
new file mode 100644
index 0000000..3d9b20e
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/TransactionalExceptionMapper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2013, 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.ext.cdi1x.transaction.internal;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.transaction.TransactionalException;
+
+import org.glassfish.jersey.ext.cdi1x.internal.JerseyVetoed;
+import org.glassfish.jersey.spi.ExceptionMappers;
+import org.glassfish.jersey.spi.ExtendedExceptionMapper;
+
+/**
+ * Helper class to handle exceptions thrown by JTA layer. If this mapper was not
+ * registered, no {@link WebApplicationException} thrown from a transactional
+ * CDI bean would get properly mapped to corresponding response.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@ApplicationScoped
+@JerseyVetoed
+public class TransactionalExceptionMapper implements ExtendedExceptionMapper<TransactionalException> {
+
+ @Inject
+ @TransactionalExceptionInterceptorProvider.WaeQualifier
+ private WebAppExceptionHolder waeHolder;
+
+ @Inject
+ private BeanManager beanManager;
+
+ @Inject
+ private Provider<ExceptionMappers> mappers;
+
+ @Override
+ public Response toResponse(TransactionalException exception) {
+ final ExceptionMapper mapper = mappers.get().findMapping(exception);
+
+ if (mapper != null && !TransactionalExceptionMapper.class.isAssignableFrom(mapper.getClass())) {
+ return mapper.toResponse(exception);
+ } else {
+ if (waeHolder != null) {
+ final WebApplicationException wae = waeHolder.getException();
+ if (wae != null) {
+ return wae.getResponse();
+ }
+ }
+ throw exception;
+ }
+ }
+
+ @Override
+ public boolean isMappable(TransactionalException exception) {
+ return true;
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/WebAppExceptionHolder.java b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/WebAppExceptionHolder.java
new file mode 100644
index 0000000..9f052df
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/WebAppExceptionHolder.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.ext.cdi1x.transaction.internal;
+
+import java.io.Serializable;
+
+import javax.ws.rs.WebApplicationException;
+
+import javax.enterprise.context.RequestScoped;
+import javax.transaction.Transactional;
+
+import org.glassfish.jersey.ext.cdi1x.internal.JerseyVetoed;
+
+/**
+ * CDI bean to store any {@link WebApplicationException}
+ * thrown in a {@link Transactional} CDI bean for later use
+ * in {@link TransactionalExceptionMapper}.
+ *
+ * @author Jakub.Podlesak (jakub.podlesak at oracle.com)
+ */
+@RequestScoped
+@JerseyVetoed
+@TransactionalExceptionInterceptorProvider.WaeQualifier
+public class WebAppExceptionHolder implements Serializable {
+
+ private static final long serialVersionUID = 31415926535879L;
+
+ private WebApplicationException exception;
+
+ /* package */ void setException(WebApplicationException exception) {
+ this.exception = exception;
+ }
+
+ /* package */ WebApplicationException getException() {
+ return exception;
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/WebAppExceptionInterceptor.java b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/WebAppExceptionInterceptor.java
new file mode 100644
index 0000000..4c68615
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/WebAppExceptionInterceptor.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.ext.cdi1x.transaction.internal;
+
+import java.io.Serializable;
+
+import javax.ws.rs.WebApplicationException;
+
+import javax.annotation.Priority;
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+import javax.transaction.Transactional;
+
+import org.glassfish.jersey.ext.cdi1x.internal.JerseyVetoed;
+
+/**
+ * Transactional interceptor to help retain {@link WebApplicationException}
+ * thrown by transactional beans.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Priority(value = Interceptor.Priority.PLATFORM_BEFORE + 199)
+@Interceptor
+@Transactional
+@JerseyVetoed
+public final class WebAppExceptionInterceptor implements Serializable {
+
+ private static final long serialVersionUID = -1L;
+
+ @Inject
+ @TransactionalExceptionInterceptorProvider.WaeQualifier
+ private WebAppExceptionHolder store;
+
+ @AroundInvoke
+ public Object intercept(final InvocationContext ic) throws Exception {
+ try {
+ return ic.proceed();
+ } catch (final WebApplicationException wae) {
+ if (store != null) {
+ store.setException(wae);
+ }
+ throw wae;
+ }
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x-transaction/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/ext/cdi/jersey-cdi1x-transaction/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
new file mode 100644
index 0000000..2d3d1b8
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.transaction.internal.TransactionalExceptionInterceptorProvider
diff --git a/ext/cdi/jersey-cdi1x-transaction/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider b/ext/cdi/jersey-cdi1x-transaction/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
new file mode 100644
index 0000000..e6b5859
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
@@ -0,0 +1,2 @@
+org.glassfish.jersey.ext.cdi1x.transaction.internal.TransactionalExceptionInterceptorProvider
+
diff --git a/ext/cdi/jersey-cdi1x-validation/pom.xml b/ext/cdi/jersey-cdi1x-validation/pom.xml
new file mode 100644
index 0000000..26d209a
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-validation/pom.xml
@@ -0,0 +1,107 @@
+<?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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.ext.cdi</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-cdi1x-validation</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-ext-cdi1x-validation</name>
+
+ <description>Jersey CDI 1.x Bean Validation Support</description>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-validator-cdi</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.enterprise</groupId>
+ <artifactId>cdi-api</artifactId>
+ <scope>provided</scope>
+ <version>1.2</version>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <!--
+ Excluding only *.tests.* packages for this module. At least one class has to exist in non-excluded
+ packages to generate proper -javadoc.jar file. See https://jira.codehaus.org/browse/MJAVADOC-329
+ -->
+ <excludePackageNames>*.tests.*</excludePackageNames>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <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>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>org.glassfish.jersey.ext.cdi1x.validation.internal;version=${project.version}</Export-Package>
+ </instructions>
+ </configuration>
+ <executions>
+ <execution>
+ <id>osgi-bundle</id>
+ <phase>package</phase>
+ <goals>
+ <goal>bundle</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/ext/cdi/jersey-cdi1x-validation/src/main/java/org/glassfish/jersey/ext/cdi1x/validation/internal/CdiInterceptorWrapper.java b/ext/cdi/jersey-cdi1x-validation/src/main/java/org/glassfish/jersey/ext/cdi1x/validation/internal/CdiInterceptorWrapper.java
new file mode 100644
index 0000000..16a098d
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-validation/src/main/java/org/glassfish/jersey/ext/cdi1x/validation/internal/CdiInterceptorWrapper.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.ext.cdi1x.validation.internal;
+
+import java.util.Set;
+
+import javax.annotation.Priority;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Inject;
+import javax.interceptor.AroundConstruct;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+
+import org.hibernate.validator.internal.cdi.interceptor.MethodValidated;
+import org.hibernate.validator.internal.cdi.interceptor.ValidationInterceptor;
+
+/**
+ * JAX-RS wrapper for Hibernate CDI bean validation interceptor.
+ * Since Jersey already executes validation on JAX-RS resources,
+ * Jersey registers this wrapper into CDI container so that JAX-RS
+ * components do not get validated twice.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@MethodValidated
+@Interceptor
+@Priority(Interceptor.Priority.PLATFORM_AFTER + 800)
+public class CdiInterceptorWrapper {
+
+ private final ValidationInterceptor interceptor;
+
+ @Inject
+ public CdiInterceptorWrapper(BeanManager beanManager) {
+ // get the original interceptor from the bean manager directly
+ // to avoid CDI bootstrap issues caused by wrong extension ordering
+ final Set<Bean<?>> interceptorBeans = beanManager.getBeans(ValidationInterceptor.class);
+ final Bean<?> interceptorBean = beanManager.resolve(interceptorBeans);
+ this.interceptor = (ValidationInterceptor) beanManager.getReference(
+ interceptorBean, ValidationInterceptor.class, beanManager.createCreationalContext(interceptorBean));
+ }
+
+ @Inject
+ private CdiInterceptorWrapperExtension extension;
+
+ @AroundInvoke
+ public Object validateMethodInvocation(InvocationContext ctx) throws Exception {
+ final boolean isJaxRsMethod = extension.jaxRsResourceCache.apply(ctx.getMethod().getDeclaringClass());
+ return isJaxRsMethod ? ctx.proceed() : interceptor.validateMethodInvocation(ctx);
+ }
+
+ @AroundConstruct
+ public void validateConstructorInvocation(InvocationContext ctx) throws Exception {
+ final boolean isJaxRsConstructor = extension.jaxRsResourceCache.apply(ctx.getConstructor().getDeclaringClass());
+ if (!isJaxRsConstructor) {
+ interceptor.validateConstructorInvocation(ctx);
+ }
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x-validation/src/main/java/org/glassfish/jersey/ext/cdi1x/validation/internal/CdiInterceptorWrapperExtension.java b/ext/cdi/jersey-cdi1x-validation/src/main/java/org/glassfish/jersey/ext/cdi1x/validation/internal/CdiInterceptorWrapperExtension.java
new file mode 100644
index 0000000..b461110
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-validation/src/main/java/org/glassfish/jersey/ext/cdi1x/validation/internal/CdiInterceptorWrapperExtension.java
@@ -0,0 +1,170 @@
+/*
+ * 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.ext.cdi1x.validation.internal;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Priority;
+import javax.enterprise.context.Dependent;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Any;
+import javax.enterprise.inject.Default;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.AfterTypeDiscovery;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.InjectionTarget;
+import javax.enterprise.util.AnnotationLiteral;
+import javax.interceptor.Interceptor;
+
+import org.glassfish.jersey.internal.util.collection.Cache;
+import org.glassfish.jersey.server.model.Resource;
+
+import org.hibernate.validator.internal.cdi.interceptor.ValidationInterceptor;
+
+/**
+ * CDI extension to register {@link CdiInterceptorWrapper}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Priority(value = Interceptor.Priority.PLATFORM_BEFORE + 199)
+public class CdiInterceptorWrapperExtension implements Extension {
+
+ public static final AnnotationLiteral<Default> DEFAULT_ANNOTATION_LITERAL = new AnnotationLiteral<Default>() {};
+ public static final AnnotationLiteral<Any> ANY_ANNOTATION_LITERAL = new AnnotationLiteral<Any>() {};
+
+ final Cache<Class<?>, Boolean> jaxRsResourceCache = new Cache<>(clazz -> Resource.from(clazz) != null);
+
+ private AnnotatedType<ValidationInterceptor> interceptorAnnotatedType;
+
+ /**
+ * Register our validation interceptor wrapper.
+ *
+ * @param beforeBeanDiscoveryEvent CDI bootstrap event.
+ * @param beanManager current bean manager.
+ */
+ private void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscoveryEvent, final BeanManager beanManager) {
+ beforeBeanDiscoveryEvent.addAnnotatedType(beanManager.createAnnotatedType(CdiInterceptorWrapper.class));
+ interceptorAnnotatedType = beanManager.createAnnotatedType(ValidationInterceptor.class);
+ beforeBeanDiscoveryEvent.addAnnotatedType(interceptorAnnotatedType);
+ }
+
+ /**
+ * Remove the original interceptor, as we are going to proxy the calls with {@link CdiInterceptorWrapper}.
+ *
+ * @param afterTypeDiscovery CDI bootstrap event.
+ */
+ private void afterTypeDiscovery(@Observes final AfterTypeDiscovery afterTypeDiscovery) {
+ afterTypeDiscovery.getInterceptors().remove(ValidationInterceptor.class);
+ }
+
+ /**
+ * Register a CDI bean for the original interceptor so that we can have it injected in our wrapper.
+ *
+ * @param afterBeanDiscovery CDI bootstrap event.
+ * @param beanManager current bean manager
+ */
+ private void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
+
+ // we need the injection target so that CDI could instantiate the original interceptor for us
+ final AnnotatedType<ValidationInterceptor> interceptorType = interceptorAnnotatedType;
+ final InjectionTarget<ValidationInterceptor> interceptorTarget = beanManager.createInjectionTarget(interceptorType);
+
+
+ afterBeanDiscovery.addBean(new Bean<ValidationInterceptor>() {
+
+ @Override
+ public Class<?> getBeanClass() {
+ return ValidationInterceptor.class;
+ }
+
+ @Override
+ public Set<InjectionPoint> getInjectionPoints() {
+ return interceptorTarget.getInjectionPoints();
+ }
+
+ @Override
+ public String getName() {
+ return "HibernateValidationInterceptorImpl";
+ }
+
+ @Override
+ public Set<Annotation> getQualifiers() {
+ return new HashSet<Annotation>() {{
+ add(DEFAULT_ANNOTATION_LITERAL);
+ add(ANY_ANNOTATION_LITERAL);
+ }};
+ }
+
+ @Override
+ public Class<? extends Annotation> getScope() {
+ return Dependent.class;
+ }
+
+ @Override
+ public Set<Class<? extends Annotation>> getStereotypes() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<Type> getTypes() {
+ return new HashSet<Type>() {{
+ add(ValidationInterceptor.class);
+ add(Object.class);
+ }};
+ }
+
+ @Override
+ public boolean isAlternative() {
+ return false;
+ }
+
+ @Override
+ public boolean isNullable() {
+ return false;
+ }
+
+ @Override
+ public ValidationInterceptor create(CreationalContext<ValidationInterceptor> ctx) {
+
+ final ValidationInterceptor result = interceptorTarget.produce(ctx);
+ interceptorTarget.inject(result, ctx);
+ interceptorTarget.postConstruct(result);
+ return result;
+ }
+
+
+ @Override
+ public void destroy(ValidationInterceptor instance,
+ CreationalContext<ValidationInterceptor> ctx) {
+
+ interceptorTarget.preDestroy(instance);
+ interceptorTarget.dispose(instance);
+ ctx.release();
+ }
+ });
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x-validation/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/ext/cdi/jersey-cdi1x-validation/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
new file mode 100644
index 0000000..7cdf9fe
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-validation/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.validation.internal.CdiInterceptorWrapperExtension
diff --git a/ext/cdi/jersey-cdi1x/pom.xml b/ext/cdi/jersey-cdi1x/pom.xml
new file mode 100644
index 0000000..11ef360
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/pom.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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.ext.cdi</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-cdi1x</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-ext-cdi1x</name>
+
+ <description>Jersey CDI 1.1 integration</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.enterprise</groupId>
+ <artifactId>cdi-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jmockit</groupId>
+ <artifactId>jmockit</artifactId>
+ <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>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>
+ org.glassfish.jersey.ext.cdi1x.spi,org.glassfish.jersey.ext.cdi1x.internal,
+ org.glassfish.jersey.ext.cdi1x.internal.spi
+ </Export-Package>
+ <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
+ <_nodefaultversion>false</_nodefaultversion>
+ </instructions>
+ </configuration>
+ <executions>
+ <execution>
+ <id>osgi-bundle</id>
+ <phase>package</phase>
+ <goals>
+ <goal>bundle</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java
new file mode 100644
index 0000000..d62c6a5
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java
@@ -0,0 +1,132 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import java.lang.annotation.Annotation;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.InjectionTarget;
+import javax.enterprise.inject.spi.InjectionTargetFactory;
+
+import org.glassfish.jersey.internal.inject.DisposableSupplier;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * Abstract supplier to provide CDI components obtained from CDI bean manager.
+ * The factory handles CDI managed components as well as non-contextual managed beans.
+ * To specify scope of provided CDI beans, an extension of this supplier
+ * should implement properly annotated {@link java.util.function.Supplier#get()} method that
+ * could just delegate to the existing {@link #_provide()} method.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public abstract class AbstractCdiBeanSupplier<T> implements DisposableSupplier<T> {
+
+ final Class<T> clazz;
+ final InstanceManager<T> referenceProvider;
+ final Annotation[] qualifiers;
+ /**
+ * Create new factory instance for given type and bean manager.
+ *
+ * @param rawType type of the components to provide.
+ * @param injectionManager actual injection manager instance.
+ * @param beanManager current bean manager to get references from.
+ * @param cdiManaged set to {@code true} if the component should be managed by CDI.
+ */
+ public AbstractCdiBeanSupplier(final Class<T> rawType,
+ final InjectionManager injectionManager,
+ final BeanManager beanManager,
+ final boolean cdiManaged) {
+
+ this.clazz = rawType;
+ this.qualifiers = CdiUtil.getQualifiers(clazz.getAnnotations());
+ this.referenceProvider = cdiManaged ? new InstanceManager<T>() {
+
+ final Iterator<Bean<?>> beans = beanManager.getBeans(clazz, qualifiers).iterator();
+ final Bean bean = beans.hasNext() ? beans.next() : null;
+
+ @Override
+ public T getInstance(final Class<T> clazz) {
+ return (bean != null) ? CdiUtil.getBeanReference(clazz, bean, beanManager) : null;
+ }
+
+ @Override
+ public void preDestroy(final T instance) {
+ // do nothing
+ }
+ } : new InstanceManager<T>() {
+
+ final AnnotatedType<T> annotatedType = beanManager.createAnnotatedType(clazz);
+ final InjectionTargetFactory<T> injectionTargetFactory = beanManager.getInjectionTargetFactory(annotatedType);
+ final InjectionTarget<T> injectionTarget = injectionTargetFactory.createInjectionTarget(null);
+
+ @Override
+ public T getInstance(final Class<T> clazz) {
+ final CreationalContext<T> creationalContext = beanManager.createCreationalContext(null);
+ final T instance = injectionTarget.produce(creationalContext);
+ injectionTarget.inject(instance, creationalContext);
+ if (injectionManager != null) {
+ injectionManager.inject(instance, CdiComponentProvider.CDI_CLASS_ANALYZER);
+ }
+ injectionTarget.postConstruct(instance);
+ return instance;
+ }
+
+ @Override
+ public void preDestroy(final T instance) {
+ injectionTarget.preDestroy(instance);
+ }
+ };
+ }
+
+ @SuppressWarnings(value = "unchecked")
+ /* package */ T _provide() {
+ final T instance = referenceProvider.getInstance(clazz);
+ if (instance != null) {
+ return instance;
+ }
+ throw new NoSuchElementException(LocalizationMessages.CDI_LOOKUP_FAILED(clazz));
+ }
+
+ @Override
+ public void dispose(final T instance) {
+ referenceProvider.preDestroy(instance);
+ }
+
+ private interface InstanceManager<T> {
+
+ /**
+ * Get me correctly instantiated and injected instance.
+ *
+ * @param clazz type of the component to instantiate.
+ * @return injected component instance.
+ */
+ T getInstance(Class<T> clazz);
+
+ /**
+ * Do whatever needs to be done before given instance is destroyed.
+ *
+ * @param instance to be destroyed.
+ */
+ void preDestroy(T instance);
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java
new file mode 100644
index 0000000..80b2126
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java
@@ -0,0 +1,949 @@
+/*
+ * Copyright (c) 2013, 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.ext.cdi1x.internal;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Context;
+
+import javax.annotation.ManagedBean;
+import javax.annotation.Priority;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Dependent;
+import javax.enterprise.context.RequestScoped;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Default;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.AfterTypeDiscovery;
+import javax.enterprise.inject.spi.Annotated;
+import javax.enterprise.inject.spi.AnnotatedCallable;
+import javax.enterprise.inject.spi.AnnotatedConstructor;
+import javax.enterprise.inject.spi.AnnotatedParameter;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.InjectionTarget;
+import javax.enterprise.inject.spi.ProcessAnnotatedType;
+import javax.enterprise.inject.spi.ProcessInjectionTarget;
+import javax.enterprise.util.AnnotationLiteral;
+import javax.inject.Qualifier;
+
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerInjectedTarget;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionTargetListener;
+import org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.Binder;
+import org.glassfish.jersey.internal.inject.Bindings;
+import org.glassfish.jersey.internal.inject.ForeignRequestScopeBridge;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.InstanceBinding;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.internal.inject.SupplierInstanceBinding;
+import org.glassfish.jersey.internal.util.collection.Cache;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.model.Parameter;
+import org.glassfish.jersey.server.model.Resource;
+import org.glassfish.jersey.server.spi.ComponentProvider;
+import org.glassfish.jersey.server.spi.internal.ValueParamProvider;
+
+import org.glassfish.hk2.api.ClassAnalyzer;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Jersey CDI integration implementation.
+ * Implements {@link ComponentProvider Jersey component provider} to serve CDI beans
+ * obtained from the actual CDI bean manager.
+ * To properly inject JAX-RS/Jersey managed beans into CDI, it also
+ * serves as a {@link Extension CDI Extension}, that intercepts CDI injection targets.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Priority(200)
+public class CdiComponentProvider implements ComponentProvider, Extension {
+
+ private static final Logger LOGGER = Logger.getLogger(CdiComponentProvider.class.getName());
+
+ /**
+ * annotation types that distinguish the classes to be added to {@link #jaxrsInjectableTypes}
+ */
+ private static final Set<Class<? extends Annotation>> JAX_RS_INJECT_ANNOTATIONS =
+ new HashSet<Class<? extends Annotation>>() {{
+ addAll(JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS);
+ add(Context.class);
+ }};
+
+ /**
+ * Name to be used when binding CDI injectee skipping class analyzer to HK2 service injection manager.
+ */
+ public static final String CDI_CLASS_ANALYZER = "CdiInjecteeSkippingClassAnalyzer";
+
+ /**
+ * set of non JAX-RS components containing JAX-RS injection points
+ */
+ private final Set<Type> jaxrsInjectableTypes = new HashSet<>();
+ private final Set<Type> hk2ProvidedTypes = Collections.synchronizedSet(new HashSet<Type>());
+ private final Set<Type> jerseyVetoedTypes = Collections.synchronizedSet(new HashSet<Type>());
+
+ /**
+ * set of request scoped components
+ */
+ private final Set<Class<?>> requestScopedComponents = new HashSet<>();
+
+
+ private final Cache<Class<?>, Boolean> jaxRsComponentCache = new Cache<>(new Function<Class<?>, Boolean>() {
+ @Override
+ public Boolean apply(final Class<?> clazz) {
+ return Application.class.isAssignableFrom(clazz)
+ || Providers.isJaxRsProvider(clazz)
+ || jaxRsResourceCache.apply(clazz);
+ }
+ });
+
+ private final Cache<Class<?>, Boolean> jaxRsResourceCache = new Cache<>(clazz -> Resource.from(clazz) != null);
+
+ private final Hk2CustomBoundTypesProvider customHk2TypesProvider;
+ private final InjectionManagerStore injectionManagerStore;
+
+ private volatile InjectionManager injectionManager;
+ private volatile javax.enterprise.inject.spi.BeanManager beanManager;
+
+ private volatile Map<Class<?>, Set<Method>> methodsToSkip = new HashMap<>();
+ private volatile Map<Class<?>, Set<Field>> fieldsToSkip = new HashMap<>();
+
+ public CdiComponentProvider() {
+ customHk2TypesProvider = CdiUtil.lookupService(Hk2CustomBoundTypesProvider.class);
+ injectionManagerStore = CdiUtil.createHk2InjectionManagerStore();
+ }
+
+ @Override
+ public void initialize(final InjectionManager injectionManager) {
+ this.injectionManager = injectionManager;
+ this.beanManager = CdiUtil.getBeanManager();
+
+ if (beanManager != null) {
+ // Try to get CdiComponentProvider created by CDI.
+ final CdiComponentProvider extension = beanManager.getExtension(CdiComponentProvider.class);
+
+ if (extension != null) {
+ extension.addInjectionManager(this.injectionManager);
+
+ this.fieldsToSkip = extension.getFieldsToSkip();
+ this.methodsToSkip = extension.getMethodsToSkip();
+
+ bindHk2ClassAnalyzer();
+
+ LOGGER.config(LocalizationMessages.CDI_PROVIDER_INITIALIZED());
+ }
+ }
+ }
+
+ /**
+ * CDI producer for CDI bean constructor String parameters, that should be injected by JAX-RS.
+ */
+ @ApplicationScoped
+ public static class JaxRsParamProducer {
+
+ @Qualifier
+ @Retention(RUNTIME)
+ @Target({METHOD, FIELD, PARAMETER, TYPE})
+ public static @interface JaxRsParamQualifier {
+ }
+
+ private static final JaxRsParamQualifier JaxRsParamQUALIFIER = new JaxRsParamQualifier() {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return JaxRsParamQualifier.class;
+ }
+ };
+
+ static final Set<Class<? extends Annotation>> JAX_RS_STRING_PARAM_ANNOTATIONS =
+ new HashSet<Class<? extends Annotation>>() {{
+ add(javax.ws.rs.PathParam.class);
+ add(javax.ws.rs.QueryParam.class);
+ add(javax.ws.rs.CookieParam.class);
+ add(javax.ws.rs.HeaderParam.class);
+ add(javax.ws.rs.MatrixParam.class);
+ add(javax.ws.rs.FormParam.class);
+ }};
+
+ /**
+ * Internal cache to store CDI {@link InjectionPoint} to Jersey {@link Parameter} mapping.
+ */
+ final Cache<InjectionPoint, Parameter> parameterCache = new Cache<>(injectionPoint -> {
+ final Annotated annotated = injectionPoint.getAnnotated();
+ final Class<?> clazz = injectionPoint.getMember().getDeclaringClass();
+
+ if (annotated instanceof AnnotatedParameter) {
+
+ final AnnotatedParameter annotatedParameter = (AnnotatedParameter) annotated;
+ final AnnotatedCallable callable = annotatedParameter.getDeclaringCallable();
+
+ if (callable instanceof AnnotatedConstructor) {
+
+ final AnnotatedConstructor ac = (AnnotatedConstructor) callable;
+ final int position = annotatedParameter.getPosition();
+ final List<Parameter> parameters = Parameter.create(clazz, clazz, ac.getJavaMember(), false);
+
+ return parameters.get(position);
+ }
+ }
+
+ return null;
+ });
+
+ /**
+ * Provide a value for given injection point. If the injection point does not refer
+ * to a CDI bean constructor parameter, or the value could not be found, the method will return null.
+ *
+ * @param injectionPoint actual injection point.
+ * @param beanManager current application bean manager.
+ * @return concrete JAX-RS parameter value for given injection point.
+ */
+ @javax.enterprise.inject.Produces
+ @JaxRsParamQualifier
+ public String getParameterValue(final InjectionPoint injectionPoint, final BeanManager beanManager) {
+ final Parameter parameter = parameterCache.apply(injectionPoint);
+
+ if (parameter != null) {
+ InjectionManager injectionManager =
+ beanManager.getExtension(CdiComponentProvider.class).getEffectiveInjectionManager();
+
+ Set<ValueParamProvider> providers = Providers.getProviders(injectionManager, ValueParamProvider.class);
+ ContainerRequest containerRequest = injectionManager.getInstance(ContainerRequest.class);
+ for (ValueParamProvider vfp : providers) {
+ Function<ContainerRequest, ?> paramValueSupplier = vfp.getValueProvider(parameter);
+ if (paramValueSupplier != null) {
+ return (String) paramValueSupplier.apply(containerRequest);
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+
+ @Override
+ public boolean bind(final Class<?> clazz, final Set<Class<?>> providerContracts) {
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.fine(LocalizationMessages.CDI_CLASS_BEING_CHECKED(clazz));
+ }
+
+ if (beanManager == null) {
+ return false;
+ }
+
+ if (isJerseyOrDependencyType(clazz)) {
+ return false;
+ }
+
+ final boolean isCdiManaged = isCdiComponent(clazz);
+ final boolean isManagedBean = isManagedBean(clazz);
+ final boolean isJaxRsComponent = isJaxRsComponentType(clazz);
+
+ if (!isCdiManaged && !isManagedBean && !isJaxRsComponent) {
+ return false;
+ }
+
+ final boolean isJaxRsResource = jaxRsResourceCache.apply(clazz);
+
+ final Class<? extends Annotation> beanScopeAnnotation = CdiUtil.getBeanScope(clazz, beanManager);
+ final boolean isRequestScoped = beanScopeAnnotation == RequestScoped.class
+ || (beanScopeAnnotation == Dependent.class && isJaxRsResource);
+
+ Supplier<AbstractCdiBeanSupplier> beanFactory = isRequestScoped
+ ? new RequestScopedCdiBeanSupplier(clazz, injectionManager, beanManager, isCdiManaged)
+ : new GenericCdiBeanSupplier(clazz, injectionManager, beanManager, isCdiManaged);
+
+ SupplierInstanceBinding<AbstractCdiBeanSupplier> builder = Bindings.supplier(beanFactory).to(clazz);
+ for (final Class contract : providerContracts) {
+ builder.to(contract);
+ }
+ injectionManager.register(builder);
+
+ if (isRequestScoped) {
+ requestScopedComponents.add(clazz);
+ }
+
+ if (LOGGER.isLoggable(Level.CONFIG)) {
+ LOGGER.config(LocalizationMessages.CDI_CLASS_BOUND_WITH_CDI(clazz));
+ }
+
+ return true;
+ }
+
+ @Override
+ public void done() {
+ if (requestScopedComponents.size() > 0) {
+ InstanceBinding<ForeignRequestScopeBridge> descriptor = Bindings
+ .service((ForeignRequestScopeBridge) () -> requestScopedComponents)
+ .to(ForeignRequestScopeBridge.class);
+
+ injectionManager.register(descriptor);
+
+ if (LOGGER.isLoggable(Level.CONFIG)) {
+ LOGGER.config(LocalizationMessages.CDI_REQUEST_SCOPED_COMPONENTS_RECOGNIZED(
+ listElements(new StringBuilder().append("\n"), requestScopedComponents).toString()));
+ }
+ }
+ }
+
+ private boolean isCdiComponent(final Class<?> component) {
+ final Annotation[] qualifiers = CdiUtil.getQualifiers(component.getAnnotations());
+ return !beanManager.getBeans(component, qualifiers).isEmpty();
+ }
+
+ private boolean isManagedBean(final Class<?> component) {
+ return component.isAnnotationPresent(ManagedBean.class);
+ }
+
+ private static AnnotatedConstructor<?> enrichedConstructor(final AnnotatedConstructor<?> ctor) {
+ return new AnnotatedConstructor(){
+
+ @Override
+ public Constructor getJavaMember() {
+ return ctor.getJavaMember();
+ }
+
+ @Override
+ public List<AnnotatedParameter> getParameters() {
+ final List<AnnotatedParameter> parameters = new ArrayList<>(ctor.getParameters().size());
+
+ for (final AnnotatedParameter<?> ap : ctor.getParameters()) {
+ parameters.add(new AnnotatedParameter() {
+
+ @Override
+ public int getPosition() {
+ return ap.getPosition();
+ }
+
+ @Override
+ public AnnotatedCallable getDeclaringCallable() {
+ return ap.getDeclaringCallable();
+ }
+
+ @Override
+ public Type getBaseType() {
+ return ap.getBaseType();
+ }
+
+ @Override
+ public Set<Type> getTypeClosure() {
+ return ap.getTypeClosure();
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(final Class<T> annotationType) {
+ if (annotationType == JaxRsParamProducer.JaxRsParamQualifier.class) {
+ return hasAnnotation(ap, JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS)
+ ? (T) JaxRsParamProducer.JaxRsParamQUALIFIER : null;
+ } else {
+ return ap.getAnnotation(annotationType);
+ }
+ }
+
+ @Override
+ public Set<Annotation> getAnnotations() {
+ final Set<Annotation> result = new HashSet<>();
+ for (final Annotation a : ap.getAnnotations()) {
+ result.add(a);
+ final Class<? extends Annotation> annotationType = a.annotationType();
+ if (JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS.contains(annotationType)) {
+ result.add(JaxRsParamProducer.JaxRsParamQUALIFIER);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public boolean isAnnotationPresent(final Class<? extends Annotation> annotationType) {
+ return (annotationType == JaxRsParamProducer.JaxRsParamQualifier.class
+ && hasAnnotation(ap, JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS))
+ || ap.isAnnotationPresent(annotationType);
+ }
+ });
+ }
+ return parameters;
+ }
+
+ @Override
+ public boolean isStatic() {
+ return ctor.isStatic();
+ }
+
+ @Override
+ public AnnotatedType getDeclaringType() {
+ return ctor.getDeclaringType();
+ }
+
+ @Override
+ public Type getBaseType() {
+ return ctor.getBaseType();
+ }
+
+ @Override
+ public Set<Type> getTypeClosure() {
+ return ctor.getTypeClosure();
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(final Class<T> annotationType) {
+ return ctor.getAnnotation(annotationType);
+ }
+
+ @Override
+ public Set<Annotation> getAnnotations() {
+ return ctor.getAnnotations();
+ }
+
+ @Override
+ public boolean isAnnotationPresent(final Class<? extends Annotation> annotationType) {
+ return ctor.isAnnotationPresent(annotationType);
+ }
+ };
+ }
+
+ @SuppressWarnings("unused")
+ private void processAnnotatedType(@Observes
+ // We can not apply the following constraint
+ // if we want to fully support {@link org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider}.
+ // Covered by tests/integration/cdi-with-jersey-injection-custom-cfg-webapp test application:
+// @WithAnnotations({
+// Context.class,
+// ApplicationPath.class,
+// HeaderParam.class,
+// QueryParam.class,
+// FormParam.class,
+// MatrixParam.class,
+// BeanParam.class,
+// PathParam.class})
+ final ProcessAnnotatedType processAnnotatedType) {
+ final AnnotatedType<?> annotatedType = processAnnotatedType.getAnnotatedType();
+
+ // if one of the JAX-RS annotations is present in the currently seen class, add it to the "whitelist"
+ if (containsJaxRsConstructorInjection(annotatedType)
+ || containsJaxRsFieldInjection(annotatedType)
+ || containsJaxRsMethodInjection(annotatedType)) {
+ jaxrsInjectableTypes.add(annotatedType.getBaseType());
+ }
+
+ if (customHk2TypesProvider != null) {
+ final Type baseType = annotatedType.getBaseType();
+ if (customHk2TypesProvider.getHk2Types().contains(baseType)) {
+ processAnnotatedType.veto();
+ jerseyVetoedTypes.add(baseType);
+ }
+ }
+
+ if (containsJaxRsParameterizedCtor(annotatedType)) {
+ processAnnotatedType.setAnnotatedType(new AnnotatedType() {
+
+ @Override
+ public Class getJavaClass() {
+ return annotatedType.getJavaClass();
+ }
+
+ @Override
+ public Set<AnnotatedConstructor> getConstructors() {
+ final Set<AnnotatedConstructor> result = new HashSet<>();
+ for (final AnnotatedConstructor c : annotatedType.getConstructors()) {
+ result.add(enrichedConstructor(c));
+ }
+ return result;
+ }
+
+ @Override
+ public Set getMethods() {
+ return annotatedType.getMethods();
+ }
+
+ @Override
+ public Set getFields() {
+ return annotatedType.getFields();
+ }
+
+ @Override
+ public Type getBaseType() {
+ return annotatedType.getBaseType();
+ }
+
+ @Override
+ public Set<Type> getTypeClosure() {
+ return annotatedType.getTypeClosure();
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(final Class<T> annotationType) {
+ return annotatedType.getAnnotation(annotationType);
+ }
+
+ @Override
+ public Set<Annotation> getAnnotations() {
+ return annotatedType.getAnnotations();
+ }
+
+ @Override
+ public boolean isAnnotationPresent(final Class<? extends Annotation> annotationType) {
+ return annotatedType.isAnnotationPresent(annotationType);
+ }
+ });
+ }
+ }
+
+ private boolean containsJaxRsParameterizedCtor(final AnnotatedType annotatedType) {
+ return containAnnotatedParameters(annotatedType.getConstructors(), JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS);
+ }
+
+ private boolean containsJaxRsConstructorInjection(final AnnotatedType annotatedType) {
+ return containAnnotatedParameters(annotatedType.getConstructors(), JAX_RS_INJECT_ANNOTATIONS);
+ }
+
+ private boolean containsJaxRsMethodInjection(final AnnotatedType annotatedType) {
+ return containAnnotatedParameters(annotatedType.getMethods(), JAX_RS_INJECT_ANNOTATIONS);
+ }
+
+ private boolean containsJaxRsFieldInjection(final AnnotatedType annotatedType) {
+ return containAnnotation(annotatedType.getFields(), JAX_RS_INJECT_ANNOTATIONS);
+ }
+
+ private boolean containAnnotatedParameters(final Collection<AnnotatedCallable> annotatedCallables,
+ final Set<Class<? extends Annotation>> annotationSet) {
+ for (final AnnotatedCallable c : annotatedCallables) {
+ if (containAnnotation(c.getParameters(), annotationSet)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean containAnnotation(final Collection<Annotated> elements,
+ final Set<Class<? extends Annotation>> annotationSet) {
+ for (final Annotated element : elements) {
+ if (hasAnnotation(element, annotationSet)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean hasAnnotation(final Annotated element, final Set<Class<? extends Annotation>> annotations) {
+ for (final Class<? extends Annotation> a : annotations) {
+ if (element.isAnnotationPresent(a)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @SuppressWarnings("unused")
+ private void afterTypeDiscovery(@Observes final AfterTypeDiscovery afterTypeDiscovery) {
+ if (LOGGER.isLoggable(Level.CONFIG) && !jerseyVetoedTypes.isEmpty()) {
+ LOGGER.config(LocalizationMessages.CDI_TYPE_VETOED(customHk2TypesProvider,
+ listElements(new StringBuilder().append("\n"), jerseyVetoedTypes).toString()));
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private void beforeBeanDiscovery(@Observes final BeforeBeanDiscovery beforeBeanDiscovery,
+ final javax.enterprise.inject.spi.BeanManager beanManager) {
+ beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(JaxRsParamProducer.class));
+ }
+
+ @SuppressWarnings("unused")
+ private void processInjectionTarget(@Observes final ProcessInjectionTarget event) {
+ final InjectionTarget it = event.getInjectionTarget();
+ final Class<?> componentClass = event.getAnnotatedType().getJavaClass();
+
+ final Set<InjectionPoint> cdiInjectionPoints = filterHk2InjectionPointsOut(it.getInjectionPoints());
+
+ for (final InjectionPoint injectionPoint : cdiInjectionPoints) {
+ final Member member = injectionPoint.getMember();
+ if (member instanceof Field) {
+ addInjecteeToSkip(componentClass, fieldsToSkip, (Field) member);
+ } else if (member instanceof Method) {
+ addInjecteeToSkip(componentClass, methodsToSkip, (Method) member);
+ }
+ }
+
+ InjectionManagerInjectedCdiTarget target = null;
+ if (isJerseyOrDependencyType(componentClass)) {
+ target = new InjectionManagerInjectedCdiTarget(it) {
+
+ @Override
+ public Set<InjectionPoint> getInjectionPoints() {
+ // Tell CDI to ignore Jersey (or it's dependencies) classes when injecting.
+ // CDI will not treat these classes as CDI beans (as they are not).
+ return Collections.emptySet();
+ }
+ };
+ } else if (isJaxRsComponentType(componentClass)
+ || jaxrsInjectableTypes.contains(event.getAnnotatedType().getBaseType())) {
+ target = new InjectionManagerInjectedCdiTarget(it) {
+
+ @Override
+ public Set<InjectionPoint> getInjectionPoints() {
+ // Inject CDI beans into JAX-RS resources/providers/application.
+ return cdiInjectionPoints;
+ }
+ };
+ }
+
+ if (target != null) {
+ notify(target);
+ //noinspection unchecked
+ event.setInjectionTarget(target);
+ }
+ }
+
+ private Set<InjectionPoint> filterHk2InjectionPointsOut(final Set<InjectionPoint> originalInjectionPoints) {
+ final Set<InjectionPoint> filteredInjectionPoints = new HashSet<>();
+ for (final InjectionPoint ip : originalInjectionPoints) {
+ final Type injectedType = ip.getType();
+ if (customHk2TypesProvider != null && customHk2TypesProvider.getHk2Types().contains(injectedType)) {
+ //remember the type, we would need to mock it's CDI binding at runtime
+ hk2ProvidedTypes.add(injectedType);
+ } else {
+ if (injectedType instanceof Class<?>) {
+ final Class<?> injectedClass = (Class<?>) injectedType;
+ if (isJerseyOrDependencyType(injectedClass)) {
+ //remember the type, we would need to mock it's CDI binding at runtime
+ hk2ProvidedTypes.add(injectedType);
+ } else {
+ filteredInjectionPoints.add(ip);
+ }
+ } else { // it is not a class, maybe provider type?:
+ if (isInjectionProvider(injectedType)
+ && (isProviderOfJerseyType((ParameterizedType) injectedType))) {
+ //remember the type, we would need to mock it's CDI binding at runtime
+ hk2ProvidedTypes.add(((ParameterizedType) injectedType).getActualTypeArguments()[0]);
+ } else {
+ filteredInjectionPoints.add(ip);
+ }
+ }
+ }
+ }
+ return filteredInjectionPoints;
+ }
+
+ private boolean isInjectionProvider(final Type injectedType) {
+ return injectedType instanceof ParameterizedType
+ && ((ParameterizedType) injectedType).getRawType() == javax.inject.Provider.class;
+ }
+
+ private boolean isProviderOfJerseyType(final ParameterizedType provider) {
+ final Type firstArgumentType = provider.getActualTypeArguments()[0];
+ if (firstArgumentType instanceof Class && isJerseyOrDependencyType((Class<?>) firstArgumentType)) {
+ return true;
+ }
+ return (customHk2TypesProvider != null && customHk2TypesProvider.getHk2Types().contains(firstArgumentType));
+ }
+
+ private <T> void addInjecteeToSkip(final Class<?> componentClass, final Map<Class<?>, Set<T>> toSkip, final T member) {
+ if (!toSkip.containsKey(componentClass)) {
+ toSkip.put(componentClass, new HashSet<T>());
+ }
+ toSkip.get(componentClass).add(member);
+ }
+
+ /**
+ * Auxiliary annotation for mocked beans used to cover Jersey/HK2 injected injection points.
+ */
+ @SuppressWarnings("serial")
+ public static class CdiDefaultAnnotation extends AnnotationLiteral<Default> implements Default {
+
+ private static final long serialVersionUID = 1L;
+ }
+
+ @SuppressWarnings({"unused", "unchecked", "rawtypes"})
+ private void afterDiscoveryObserver(@Observes final AfterBeanDiscovery abd) {
+ if (customHk2TypesProvider != null) {
+ hk2ProvidedTypes.addAll(customHk2TypesProvider.getHk2Types());
+ }
+
+ for (final Type t : hk2ProvidedTypes) {
+ abd.addBean(new Hk2Bean(t));
+ }
+ }
+
+ /**
+ * Gets you fields to skip from a proxied instance.
+ * <p/>
+ * Note: Do NOT lower the visibility of this method. CDI proxies need at least this visibility.
+ *
+ * @return fields to skip when injecting via HK2
+ */
+ /* package */ Map<Class<?>, Set<Field>> getFieldsToSkip() {
+ return fieldsToSkip;
+ }
+
+ /**
+ * Gets you methods to skip (from a proxied instance).
+ * <p/>
+ * Note: Do NOT lower the visibility of this method. CDI proxies need at least this visibility.
+ *
+ * @return methods to skip when injecting via HK2
+ */
+ /* package */ Map<Class<?>, Set<Method>> getMethodsToSkip() {
+ return methodsToSkip;
+ }
+
+ /**
+ * Gets you effective injection manager.
+ * <p/>
+ * Note: Do NOT lower the visibility of this method. CDI proxies need at least this visibility.
+ *
+ * @return HK2 injection manager.
+ */
+ /* package */ InjectionManager getEffectiveInjectionManager() {
+ return injectionManagerStore.getEffectiveInjectionManager();
+ }
+
+ /**
+ * Add HK2 {@link InjectionManager injection manager} (to a proxied instance).
+ * <p/>
+ * Note: Do NOT lower the visibility of this method. CDI proxies need at least this visibility.
+ *
+ * @param injectionManager injection manager.
+ */
+ /* package */ void addInjectionManager(final InjectionManager injectionManager) {
+ injectionManagerStore.registerInjectionManager(injectionManager);
+ }
+
+ /**
+ * Notifies the {@code InjectionTargetListener injection target listener} about new
+ * {@link InjectionManagerInjectedTarget injected target}.
+ * <p/>
+ * Note: Do NOT lower the visibility of this method. CDI proxies need at least this visibility.
+ *
+ * @param target new injected target.
+ */
+ /* package */ void notify(final InjectionManagerInjectedTarget target) {
+ if (injectionManagerStore instanceof InjectionTargetListener) {
+ ((InjectionTargetListener) injectionManagerStore).notify(target);
+ }
+ }
+
+ /**
+ * Introspect given type to determine if it represents a JAX-RS component.
+ *
+ * @param clazz type to be introspected.
+ * @return true if the type represents a JAX-RS component type.
+ */
+ /* package */ boolean isJaxRsComponentType(final Class<?> clazz) {
+ return jaxRsComponentCache.apply(clazz);
+ }
+
+ private static boolean isJerseyOrDependencyType(final Class<?> clazz) {
+ if (clazz.isPrimitive() || clazz.isSynthetic()) {
+ return false;
+ }
+
+ final Package pkg = clazz.getPackage();
+ if (pkg == null) { // Class.getPackage() could return null
+ LOGGER.warning(String.format("Class %s has null package", clazz));
+ return false;
+ }
+
+ final String pkgName = pkg.getName();
+ return !clazz.isAnnotationPresent(JerseyVetoed.class)
+ && (pkgName.contains("org.glassfish.hk2")
+ || pkgName.contains("jersey.repackaged")
+ || pkgName.contains("org.jvnet.hk2")
+ || (pkgName.startsWith("org.glassfish.jersey")
+ && !pkgName.startsWith("org.glassfish.jersey.examples")
+ && !pkgName.startsWith("org.glassfish.jersey.tests"))
+ || (pkgName.startsWith("com.sun.jersey")
+ && !pkgName.startsWith("com.sun.jersey.examples")
+ && !pkgName.startsWith("com.sun.jersey.tests")));
+ }
+
+ private void bindHk2ClassAnalyzer() {
+ ClassAnalyzer defaultClassAnalyzer =
+ injectionManager.getInstance(ClassAnalyzer.class, ClassAnalyzer.DEFAULT_IMPLEMENTATION_NAME);
+
+ int skippedElements = methodsToSkip.size() + fieldsToSkip.size();
+
+ ClassAnalyzer customizedClassAnalyzer = skippedElements > 0
+ ? new InjecteeSkippingAnalyzer(defaultClassAnalyzer, methodsToSkip, fieldsToSkip)
+ : defaultClassAnalyzer;
+
+ Binder binder = new AbstractBinder() {
+ @Override
+ protected void configure() {
+ bind(customizedClassAnalyzer)
+ .analyzeWith(ClassAnalyzer.DEFAULT_IMPLEMENTATION_NAME)
+ .to(ClassAnalyzer.class)
+ .named(CDI_CLASS_ANALYZER);
+ }
+ };
+ injectionManager.register(binder);
+ }
+
+ private StringBuilder listElements(final StringBuilder logMsgBuilder, final Collection<? extends Object> elements) {
+ for (final Object t : elements) {
+ logMsgBuilder.append(String.format(" - %s%n", t));
+ }
+ return logMsgBuilder;
+ }
+
+ @SuppressWarnings("unchecked")
+ private abstract class InjectionManagerInjectedCdiTarget implements InjectionManagerInjectedTarget {
+
+ private final InjectionTarget delegate;
+ private volatile InjectionManager effectiveInjectionManager;
+
+ public InjectionManagerInjectedCdiTarget(InjectionTarget delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public abstract Set<InjectionPoint> getInjectionPoints();
+
+ @Override
+ public void inject(final Object t, final CreationalContext cc) {
+ delegate.inject(t, cc);
+
+ InjectionManager injectingManager = getEffectiveInjectionManager();
+ if (injectingManager == null) {
+ injectingManager = effectiveInjectionManager;
+ }
+
+ if (injectingManager != null) {
+ injectingManager.inject(t, CdiComponentProvider.CDI_CLASS_ANALYZER);
+ }
+ }
+
+ @Override
+ public void postConstruct(final Object t) {
+ delegate.postConstruct(t);
+ }
+
+ @Override
+ public void preDestroy(final Object t) {
+ delegate.preDestroy(t);
+ }
+
+ @Override
+ public Object produce(final CreationalContext cc) {
+ return delegate.produce(cc);
+ }
+
+ @Override
+ public void dispose(final Object t) {
+ delegate.dispose(t);
+ }
+
+ @Override
+ public void setInjectionManager(final InjectionManager injectionManager) {
+ this.effectiveInjectionManager = injectionManager;
+ }
+ }
+
+ private class Hk2Bean implements Bean {
+
+ private final Type t;
+
+ public Hk2Bean(final Type t) {
+ this.t = t;
+ }
+
+ @Override
+ public Class getBeanClass() {
+ return (Class) t;
+ }
+
+ @Override
+ public Set getInjectionPoints() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean isNullable() {
+ return true;
+ }
+
+ @Override
+ public Object create(final CreationalContext creationalContext) {
+ return getEffectiveInjectionManager().getInstance(t);
+ }
+
+ @Override
+ public void destroy(final Object instance, final CreationalContext creationalContext) {
+ }
+
+ @Override
+ public Set getTypes() {
+ return Collections.singleton(t);
+ }
+
+ @Override
+ public Set getQualifiers() {
+ return Collections.singleton(new CdiDefaultAnnotation());
+ }
+
+ @Override
+ public Class getScope() {
+ return Dependent.class;
+ }
+
+ @Override
+ public String getName() {
+ return t.toString();
+ }
+
+ @Override
+ public Set getStereotypes() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean isAlternative() {
+ return false;
+ }
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiUtil.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiUtil.java
new file mode 100644
index 0000000..7f13ba6
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiUtil.java
@@ -0,0 +1,149 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Qualifier;
+
+import org.glassfish.jersey.ext.cdi1x.internal.spi.BeanManagerProvider;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.internal.ServiceFinder;
+import org.glassfish.jersey.model.internal.RankedComparator;
+import org.glassfish.jersey.model.internal.RankedProvider;
+
+/**
+ * Common CDI utility methods.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Michal Gajdos
+ */
+public final class CdiUtil {
+
+ private static final BeanManagerProvider BEAN_MANAGER_PROVIDER = new DefaultBeanManagerProvider();
+
+ /**
+ * Prevent instantiation.
+ */
+ private CdiUtil() {
+ throw new AssertionError("No instances allowed.");
+ }
+
+ /**
+ * Get me list of qualifiers included in given annotation list.
+ *
+ * @param annotations list of annotations to introspect
+ * @return annotations from the input list that are marked as qualifiers
+ */
+ public static Annotation[] getQualifiers(final Annotation[] annotations) {
+ final List<Annotation> result = new ArrayList<>(annotations.length);
+ for (final Annotation a : annotations) {
+ if (a.annotationType().isAnnotationPresent(Qualifier.class)) {
+ result.add(a);
+ }
+ }
+ return result.toArray(new Annotation[result.size()]);
+ }
+
+ /**
+ * Get me current bean manager. Method first tries to lookup available providers via {@code META-INF/services}. If not found
+ * the bean manager is returned from the default provider.
+ *
+ * @return bean manager
+ */
+ public static BeanManager getBeanManager() {
+ final BeanManagerProvider provider = lookupService(BeanManagerProvider.class);
+ if (provider != null) {
+ return provider.getBeanManager();
+ }
+
+ return BEAN_MANAGER_PROVIDER.getBeanManager();
+ }
+
+ /**
+ * Create new instance of {@link InjectionManagerStore}. Method first tries to lookup
+ * available manager via {@code META-INF/services} and if not found a new instance of default one is returned.
+ *
+ * @return an instance of injection manager store.
+ */
+ static InjectionManagerStore createHk2InjectionManagerStore() {
+ final InjectionManagerStore manager = lookupService(InjectionManagerStore.class);
+ return manager != null ? manager : new SingleInjectionManagerStore();
+ }
+
+ /**
+ * Look for a service of given type. If more then one service is found the method sorts them are returns the one with highest
+ * priority.
+ *
+ * @param clazz type of service to look for.
+ * @param <T> type of service to look for
+ * @return instance of service with highest priority or {@code null} if service of given type cannot be found.
+ * @see javax.annotation.Priority
+ */
+ static <T> T lookupService(final Class<T> clazz) {
+ final List<RankedProvider<T>> providers = new LinkedList<>();
+
+ for (final T provider : ServiceFinder.find(clazz)) {
+ providers.add(new RankedProvider<>(provider));
+ }
+ Collections.sort(providers, new RankedComparator<T>(RankedComparator.Order.ASCENDING));
+
+ return providers.isEmpty() ? null : providers.get(0).getProvider();
+ }
+
+ /**
+ * Obtain a bean reference of given type from the bean manager.
+ *
+ * @param clazz type of the bean to get reference to.
+ * @param bean the {@link Bean} object representing the managed bean.
+ * @param beanManager bean manager used to obtain an instance of the requested bean.
+ * @param <T> type of the bean to be returned.
+ * @return a bean reference or {@code null} if a bean instance cannot be found.
+ */
+ static <T> T getBeanReference(final Class<T> clazz, final Bean bean, final BeanManager beanManager) {
+ final CreationalContext<?> creationalContext = beanManager.createCreationalContext(bean);
+ final Object result = beanManager.getReference(bean, clazz, creationalContext);
+
+ return clazz.cast(result);
+ }
+
+ /**
+ * Get me scope of a bean corresponding to given class.
+ *
+ * @param beanClass bean class in question.
+ * @param beanManager actual bean manager.
+ * @return actual bean scope or null, if the scope could not be determined.
+ */
+ public static Class<? extends Annotation> getBeanScope(final Class<?> beanClass, final BeanManager beanManager) {
+ final Set<Bean<?>> beans = beanManager.getBeans(beanClass);
+ if (beans.isEmpty()) {
+ return null;
+ }
+ for (Bean b : beans) {
+ return b.getScope();
+ }
+ return null;
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/DefaultBeanManagerProvider.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/DefaultBeanManagerProvider.java
new file mode 100644
index 0000000..7fd8087
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/DefaultBeanManagerProvider.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.ext.cdi1x.internal;
+
+import java.util.logging.Logger;
+
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.CDI;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.glassfish.jersey.ext.cdi1x.internal.spi.BeanManagerProvider;
+
+/**
+ * Default implementation of {@link BeanManagerProvider} that works on most environments.
+ * At first the implementation tries to lookup the bean manager in JNDI, then via CDI 1.1 API. If not found {@code null} is
+ * returned.
+ *
+ * @author Michal Gajdos
+ * @since 2.17
+ */
+final class DefaultBeanManagerProvider implements BeanManagerProvider {
+
+ private static final Logger LOGGER = Logger.getLogger(DefaultBeanManagerProvider.class.getName());
+
+ @Override
+ public BeanManager getBeanManager() {
+ InitialContext initialContext = null;
+ try {
+ initialContext = new InitialContext();
+ return (BeanManager) initialContext.lookup("java:comp/BeanManager");
+ } catch (final Exception ex) {
+ try {
+ return CDI.current().getBeanManager();
+ } catch (final Exception e) {
+ LOGGER.config(LocalizationMessages.CDI_BEAN_MANAGER_JNDI_LOOKUP_FAILED());
+ return null;
+ }
+ } finally {
+ if (initialContext != null) {
+ try {
+ initialContext.close();
+ } catch (final NamingException ignored) {
+ // no-op
+ }
+ }
+ }
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/GenericCdiBeanSupplier.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/GenericCdiBeanSupplier.java
new file mode 100644
index 0000000..1b3d374
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/GenericCdiBeanSupplier.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.ext.cdi1x.internal;
+
+import javax.enterprise.inject.Vetoed;
+import javax.enterprise.inject.spi.BeanManager;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * Supplier to provide CDI managed components where
+ * there is no clear mapping between the CDI and scopes.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Vetoed
+public final class GenericCdiBeanSupplier extends AbstractCdiBeanSupplier {
+
+ public GenericCdiBeanSupplier(Class rawType,
+ InjectionManager injectionManager,
+ BeanManager beanManager,
+ boolean cdiManaged) {
+ super(rawType, injectionManager, beanManager, cdiManaged);
+ }
+
+ @Override
+ public Object get() {
+ return _provide();
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/GenericInjectionManagerStore.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/GenericInjectionManagerStore.java
new file mode 100644
index 0000000..3b75a6a
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/GenericInjectionManagerStore.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.ext.cdi1x.internal;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerInjectedTarget;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionTargetListener;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * Generic {@link InjectionManagerStore injection manager store} that allows multiple
+ * injection managers to run in parallel. {@link #lookupInjectionManager()}
+ * method must be implemented that shall be utilized at runtime in the case that more than a single
+ * injection manager has been registered.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @since 2.20
+ */
+public abstract class GenericInjectionManagerStore implements InjectionManagerStore, InjectionTargetListener {
+
+ private final List<InjectionManagerInjectedTarget> injectionTargets;
+
+ private volatile InjectionManager injectionManager;
+
+ private volatile boolean multipleInjectionManagers = false;
+
+ public GenericInjectionManagerStore() {
+ injectionTargets = new LinkedList<>();
+ }
+
+ @Override
+ public void registerInjectionManager(final InjectionManager injectionManager) {
+ if (!multipleInjectionManagers) {
+ if (this.injectionManager == null) { // first one
+ this.injectionManager = injectionManager;
+ } else { // second one
+ this.injectionManager = null;
+ multipleInjectionManagers = true;
+ } // first and second case
+ }
+
+ // pass the injection manager to registered injection targets anyway
+ for (final InjectionManagerInjectedTarget target : injectionTargets) {
+ target.setInjectionManager(injectionManager);
+ }
+ }
+
+ @Override
+ public InjectionManager getEffectiveInjectionManager() {
+ return !multipleInjectionManagers ? injectionManager : lookupInjectionManager();
+ }
+
+ /**
+ * CDI container specific method to obtain the actual injection manager
+ * belonging to the Jersey application where the current HTTP requests
+ * is being processed.
+ *
+ * @return actual injection manager.
+ */
+ public abstract InjectionManager lookupInjectionManager();
+
+ @Override
+ public void notify(final InjectionManagerInjectedTarget target) {
+
+ injectionTargets.add(target);
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/InjecteeSkippingAnalyzer.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/InjecteeSkippingAnalyzer.java
new file mode 100644
index 0000000..7df7446
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/InjecteeSkippingAnalyzer.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2013, 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.ext.cdi1x.internal;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.glassfish.jersey.internal.util.collection.Views;
+
+import org.glassfish.hk2.api.ClassAnalyzer;
+import org.glassfish.hk2.api.MultiException;
+
+/**
+ * Class analyzer that ignores given injection points.
+ * Used for CDI integration, where we need to avoid HK2 replacing CDI injected entities.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public final class InjecteeSkippingAnalyzer implements ClassAnalyzer {
+
+ private final ClassAnalyzer defaultAnalyzer;
+ private final Map<Class<?>, Set<Method>> methodsToSkip;
+ private final Map<Class<?>, Set<Field>> fieldsToSkip;
+
+ public InjecteeSkippingAnalyzer(ClassAnalyzer defaultAnalyzer,
+ Map<Class<?>, Set<Method>> methodsToSkip,
+ Map<Class<?>, Set<Field>> fieldsToSkip) {
+ this.defaultAnalyzer = defaultAnalyzer;
+ this.methodsToSkip = methodsToSkip;
+ this.fieldsToSkip = fieldsToSkip;
+ }
+
+ @Override
+ public <T> Constructor<T> getConstructor(Class<T> type) throws MultiException, NoSuchMethodException {
+ throw new IllegalStateException(LocalizationMessages.CDI_CLASS_ANALYZER_MISUSED());
+ }
+
+ @Override
+ public <T> Set<Method> getInitializerMethods(Class<T> type) throws MultiException {
+ final Set<Method> originalMethods = defaultAnalyzer.getInitializerMethods(type);
+ final Set<Method> skippedMethods = getMembersToSkip(type, methodsToSkip);
+ return Views.setDiffView(originalMethods, skippedMethods);
+ }
+
+ @Override
+ public <T> Set<Field> getFields(Class<T> type) throws MultiException {
+ final Set<Field> originalFields = defaultAnalyzer.getFields(type);
+ final Set<Field> skippedFields = getMembersToSkip(type, fieldsToSkip);
+ return Views.setDiffView(originalFields, skippedFields);
+ }
+
+ @Override
+ public <T> Method getPostConstructMethod(Class<T> type) throws MultiException {
+ throw new IllegalStateException(LocalizationMessages.CDI_CLASS_ANALYZER_MISUSED());
+ }
+
+ @Override
+ public <T> Method getPreDestroyMethod(Class<T> type) throws MultiException {
+ throw new IllegalStateException(LocalizationMessages.CDI_CLASS_ANALYZER_MISUSED());
+ }
+
+ private <M extends Member> Set<M> getMembersToSkip(final Class<?> type, final Map<Class<?>, Set<M>> skippedMembers) {
+
+ final Set<M> directResult = skippedMembers.get(type);
+
+ if (directResult != null) {
+ return directResult;
+ }
+
+ // fallback for GLASSFISH-20255
+ final Set<M> compositeResult = new HashSet<>();
+ for (Entry<Class<?>, Set<M>> type2Method : skippedMembers.entrySet()) {
+
+ if (type2Method.getKey().isAssignableFrom(type)) {
+ compositeResult.addAll(type2Method.getValue());
+ }
+ }
+
+ return compositeResult;
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/JerseyVetoed.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/JerseyVetoed.java
new file mode 100644
index 0000000..f294155
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/JerseyVetoed.java
@@ -0,0 +1,33 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use this to annotate types included in Jersey libraries
+ * that are to be CDI managed as defined by the {@link CdiComponentProvider}
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JerseyVetoed {
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/RequestScopedCdiBeanSupplier.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/RequestScopedCdiBeanSupplier.java
new file mode 100644
index 0000000..ffe0385
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/RequestScopedCdiBeanSupplier.java
@@ -0,0 +1,48 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import javax.enterprise.inject.Vetoed;
+import javax.enterprise.inject.spi.BeanManager;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.process.internal.RequestScoped;
+
+/**
+ * Supplier to provide CDI managed components
+ * that should be mapped into Jersey request scope.
+ * For these components, Jersey will avoid
+ * injecting dynamic proxies for JAX-RS request scoped injectees.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Vetoed
+public final class RequestScopedCdiBeanSupplier extends AbstractCdiBeanSupplier {
+
+ public RequestScopedCdiBeanSupplier(Class rawType,
+ InjectionManager locator,
+ BeanManager beanManager,
+ boolean cdiManaged) {
+ super(rawType, locator, beanManager, cdiManaged);
+ }
+
+ @Override
+ @RequestScoped
+ public Object get() {
+ return _provide();
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/SingleInjectionManagerStore.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/SingleInjectionManagerStore.java
new file mode 100644
index 0000000..011c011
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/SingleInjectionManagerStore.java
@@ -0,0 +1,48 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import javax.ws.rs.WebApplicationException;
+
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * Default {@link InjectionManagerStore injection manager} that assumes only one
+ * {@link InjectionManager injection manager} per application is used.
+ *
+ * @author Michal Gajdos
+ * @since 2.17
+ */
+final class SingleInjectionManagerStore implements InjectionManagerStore {
+
+ private volatile InjectionManager injectionManager;
+
+ @Override
+ public void registerInjectionManager(final InjectionManager injectionManager) {
+ if (this.injectionManager == null) {
+ this.injectionManager = injectionManager;
+ } else if (this.injectionManager != injectionManager) {
+ throw new WebApplicationException(LocalizationMessages.CDI_MULTIPLE_LOCATORS_INTO_SIMPLE_APP());
+ }
+ }
+
+ @Override
+ public InjectionManager getEffectiveInjectionManager() {
+ return injectionManager;
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/package-info.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/package-info.java
new file mode 100644
index 0000000..979906e
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/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 internal package supporting Jersey CDI integration.
+ */
+package org.glassfish.jersey.ext.cdi1x.internal;
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/BeanManagerProvider.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/BeanManagerProvider.java
new file mode 100644
index 0000000..cf953c4
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/BeanManagerProvider.java
@@ -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
+ */
+
+package org.glassfish.jersey.ext.cdi1x.internal.spi;
+
+import javax.enterprise.inject.spi.BeanManager;
+
+/**
+ * Provider SPI for CDI {@link javax.enterprise.inject.spi.BeanManager} for the current context.
+ * Implementations can decide how to obtain bean manager (e.g. {@link javax.naming.InitialContext}, CDI 1.1 API, ...).
+ *
+ * @author Michal Gajdos
+ * @since 2.17
+ */
+public interface BeanManagerProvider {
+
+ /**
+ * Get the CDI {@link javax.enterprise.inject.spi.BeanManager bean manager} for the current context.
+ *
+ * @return bean manager for the current context or {@code null} if no bean manager is available.
+ */
+ public BeanManager getBeanManager();
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionManagerInjectedTarget.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionManagerInjectedTarget.java
new file mode 100644
index 0000000..34d2ea6
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionManagerInjectedTarget.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.ext.cdi1x.internal.spi;
+
+import javax.enterprise.inject.spi.InjectionTarget;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * CDI {@link javax.enterprise.inject.spi.InjectionTarget injection target} that is co-injected by injection manager.
+ * The injection is done via given {@link InjectionManager injection manager}.
+ *
+ * @author Michal Gajdos
+ */
+public interface InjectionManagerInjectedTarget extends InjectionTarget {
+
+ /**
+ * Set the locator to be used to co-inject this injection target.
+ *
+ * @param injectionManager effective injection manager.
+ */
+ void setInjectionManager(InjectionManager injectionManager);
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionManagerStore.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionManagerStore.java
new file mode 100644
index 0000000..d282565
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionManagerStore.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.ext.cdi1x.internal.spi;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * {@link InjectionManager injection manager} designed for Jersey
+ * {@link javax.enterprise.inject.spi.Extension CDI extension}. This SPI is designed to support deployments that can contain
+ * more than one Jersey/InjectionManager managed CDI {@link org.glassfish.jersey.server.spi.ComponentProvider component provider}
+ * (more injection manager) but only single CDI extension instance (e.g. EAR with multiple WARs). Each CDI component provider
+ * instance acknowledges the manager about new injection manager and manager is supposed to return the effective injection manager
+ * for the current context (based on the Servlet context, for example).
+ *
+ * @author Michal Gajdos
+ * @since 2.17
+ */
+public interface InjectionManagerStore {
+
+ /**
+ * Register a new {@link InjectionManager injection manager} with this manager.
+ *
+ * @param injectionManager injection manager to be registered.
+ */
+ public void registerInjectionManager(InjectionManager injectionManager);
+
+ /**
+ * Obtain the effective {@link InjectionManager injection manager}. The implementations are supposed to
+ * decide which of the registered injection managers is the currently effective locator. The decision can be based, for
+ * example, on current Servlet context (if the application is deployed on Servlet container).
+ *
+ * @return currently effective injection manager.
+ */
+ public InjectionManager getEffectiveInjectionManager();
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionTargetListener.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionTargetListener.java
new file mode 100644
index 0000000..1cbdb70
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionTargetListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.ext.cdi1x.internal.spi;
+
+/**
+ * An extension interface for implementations of {@link InjectionManagerStore}. HK2 locator
+ * managers implementing this interface are notified when an {@link javax.enterprise.inject.spi.InjectionTarget injection target}
+ * is processed by {@link org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider}. Locator managers can then set the
+ * effective injection manager to the processed {@link InjectionManagerInjectedTarget target}.
+ *
+ * @author Michal Gajdos
+ */
+public interface InjectionTargetListener {
+
+ /**
+ * Notify the HK2 locator manager about new injection target being processed.
+ *
+ * @param target processed injection target.
+ */
+ public void notify(final InjectionManagerInjectedTarget target);
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/package-info.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/package-info.java
new file mode 100644
index 0000000..f8f0078
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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
+ */
+
+/**
+ * Jersey internal SPI to support custom defined HK2 injection binding for Jersey/CDI applications.
+ */
+package org.glassfish.jersey.ext.cdi1x.internal.spi;
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/spi/Hk2CustomBoundTypesProvider.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/spi/Hk2CustomBoundTypesProvider.java
new file mode 100644
index 0000000..dcdad4b
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/spi/Hk2CustomBoundTypesProvider.java
@@ -0,0 +1,52 @@
+/*
+ * 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.ext.cdi1x.spi;
+
+import java.lang.reflect.Type;
+import java.util.Set;
+
+/**
+ * Helper SPI to help specify Jersey HK2 custom bound types that should
+ * be HK2-injectable into CDI components.
+ *
+ * <p>Implementation of this type must be registered via META-INF/services
+ * mechanism. I.e. fully qualified name of an implementation class
+ * must be written into <code>META-INF/services/org.glassfish.jersey.ext.cdi11.spi.Hk2CustomBoundTypesProvider</code>
+ * file.
+ *
+ * <p>If more than one implementation is found, only a single one is selected that has the highest priority.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public interface Hk2CustomBoundTypesProvider {
+
+ /**
+ * Provide a set of types that should became accessible
+ * by CDI container in a form of CDI beans backed by HK2.
+ *
+ * <p>Jersey will ask CDI container to veto these types
+ * and will register HK2 backed beans into CDI, so that @{@link javax.inject.Inject}
+ * marked injection points could be satisfied.
+ *
+ * <p>The end user is responsible for defining necessary HK2 bindings
+ * within Jersey application. Should any of such bindings remain
+ * undefined, runtime errors are likely to occur.
+ *
+ * @return set of types for which HK2 backed CDI beans shall be registered.
+ */
+ public Set<Type> getHk2Types();
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/spi/package-info.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/spi/package-info.java
new file mode 100644
index 0000000..81e0dbf
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/spi/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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
+ */
+
+/**
+ * Jersey package to support custom defined HK2 injection binding
+ * for Jersey/CDI applications.
+ */
+package org.glassfish.jersey.ext.cdi1x.spi;
diff --git a/ext/cdi/jersey-cdi1x/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/ext/cdi/jersey-cdi1x/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
new file mode 100644
index 0000000..64adbff
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider
diff --git a/ext/cdi/jersey-cdi1x/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider b/ext/cdi/jersey-cdi1x/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
new file mode 100644
index 0000000..64adbff
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider
diff --git a/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties b/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties
new file mode 100644
index 0000000..82ac7de
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2013, 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
+#
+
+cdi.bean.manager.jndi.lookup.failed=Failed to obtain BeanManager from JNDI lookup.
+cdi.class.analyzer.misused=Jersey CDI class analyzer is supposed to be used only for field and method inspection when injecting CDI beans.
+cdi.class.being.checked=Class, {0}, is being checked with Jersey CDI component provider.
+cdi.class.bound.with.cdi=Class, {0}, has been bound by Jersey CDI component provider.
+cdi.hk2.bean.registered=CDI beans backed by HK2 have been registered for the following types: {0}
+cdi.lookup.failed=Error when lookup instance of class, {0}, in CDI.
+cdi.multiple.locators.into.simple.app=Trying to register multiple service locators into single service locator application.
+cdi.provider.initialized=Jersey CDI component provider initialized.
+cdi.request.scoped.components.recognized=The following CDI types were recognized as request scoped components in Jersey: {0}.
+cdi.type.vetoed=The following types have been vetoed by Jersey as per {0} configuration: {1}
diff --git a/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProviderTest.java b/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProviderTest.java
new file mode 100644
index 0000000..9242f82
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProviderTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2013, 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.ext.cdi1x.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.Path;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+
+import org.junit.Test;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test for {@link CdiComponentProvider}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class CdiComponentProviderTest {
+
+ public static class MyMessageBodyReader implements 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 type,
+ final Type genericType,
+ final Annotation[] annotations,
+ final MediaType mediaType,
+ final MultivaluedMap httpHeaders,
+ final InputStream entityStream) throws IOException, WebApplicationException {
+ return new Object();
+ }
+ }
+
+ public static class MyOtherMessageBodyReader extends MyMessageBodyReader {
+ }
+
+ public static class MyPojo {
+ }
+
+ public static class LocatorSubResource {
+
+ @Path("/")
+ public Object locator() {
+ return this;
+ }
+ }
+
+ @Path("/")
+ public static class ResourceMethodResource {
+
+ @GET
+ public Object get() {
+ return this;
+ }
+ }
+
+ public static class ResourceMethodSubResource {
+
+ @GET
+ public Object get() {
+ return this;
+ }
+ }
+
+ @Target({ElementType.METHOD})
+ @Retention(RetentionPolicy.RUNTIME)
+ @HttpMethod(HttpMethod.DELETE)
+ public @interface BINGO {
+ }
+
+ public static class CustomResourceMethodSubResource {
+
+ @BINGO
+ public Object get() {
+ return this;
+ }
+ }
+
+ /**
+ * Test provider detection.
+ */
+ @Test
+ public void testProviders() {
+ final CdiComponentProvider provider = new CdiComponentProvider();
+ assertFalse(provider.isJaxRsComponentType(MyPojo.class));
+ assertTrue(provider.isJaxRsComponentType(MyMessageBodyReader.class));
+ assertTrue(provider.isJaxRsComponentType(MyOtherMessageBodyReader.class));
+ }
+
+ /**
+ * Test sub-resource detection.
+ */
+ @Test
+ public void testResources() {
+ final CdiComponentProvider provider = new CdiComponentProvider();
+ assertTrue(provider.isJaxRsComponentType(LocatorSubResource.class));
+ assertTrue(provider.isJaxRsComponentType(ResourceMethodResource.class));
+ assertTrue(provider.isJaxRsComponentType(ResourceMethodSubResource.class));
+ assertTrue(provider.isJaxRsComponentType(CustomResourceMethodSubResource.class));
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/CdiUtilTest.java b/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/CdiUtilTest.java
new file mode 100644
index 0000000..0cdafe1
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/CdiUtilTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import javax.annotation.Priority;
+import javax.enterprise.inject.spi.BeanManager;
+
+import org.glassfish.jersey.ext.cdi1x.internal.spi.BeanManagerProvider;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import mockit.Mock;
+import mockit.MockUp;
+import mockit.Mocked;
+import mockit.Verifications;
+
+/**
+ * Unit tests for {@link org.glassfish.jersey.ext.cdi1x.internal.CdiUtil}.
+ *
+ * @author Michal Gajdos
+ */
+public class CdiUtilTest {
+
+ public static class TestBeanManagerProvider implements BeanManagerProvider {
+
+ @Override
+ public BeanManager getBeanManager() {
+ throw new RuntimeException("BeanManager!");
+ }
+ }
+
+ @Test
+ public void getBeanManagerCustom(@Mocked final TestBeanManagerProvider custom,
+ @Mocked final DefaultBeanManagerProvider fallback) throws Exception {
+ CdiUtil.getBeanManager();
+
+ new Verifications() {{
+ custom.getBeanManager(); times = 1;
+ fallback.getBeanManager(); times = 0;
+ }};
+ }
+
+ @Test
+ public void getDefaultBeanManagerDefault(@Mocked final DefaultBeanManagerProvider fallback) throws Exception {
+ new MockUp<CdiUtil>() {
+ @Mock
+ @SuppressWarnings("UnusedDeclaration")
+ <T> T lookupService(final Class<T> clazz) {
+ return null;
+ }
+ };
+
+ CdiUtil.getBeanManager();
+
+ new Verifications() {{
+ fallback.getBeanManager(); times = 1;
+ }};
+ }
+
+ @Priority(500)
+ public static class MyServiceOne implements MyService {
+ }
+
+ @Priority(100)
+ public static class MyServiceTwo implements MyService {
+ }
+
+ @Priority(300)
+ public static class MyServiceThree implements MyService {
+ }
+
+ @Test
+ public void testLookupService() throws Exception {
+ assertThat(CdiUtil.lookupService(MyService.class), instanceOf(MyServiceTwo.class));
+ }
+
+ @Test
+ public void testLookupServiceNegative() throws Exception {
+ assertThat(CdiUtil.lookupService(CdiUtil.class), nullValue());
+ }
+
+ public static class TestInjectionManagerStore implements InjectionManagerStore {
+
+ @Override
+ public void registerInjectionManager(final InjectionManager injectionManager) {
+ }
+
+ @Override
+ public InjectionManager getEffectiveInjectionManager() {
+ return null;
+ }
+ }
+
+ @Test
+ public void createHk2LocatorManagerCustom() throws Exception {
+ assertThat(CdiUtil.createHk2InjectionManagerStore(), instanceOf(TestInjectionManagerStore.class));
+ }
+
+ @Test
+ public void createHk2LocatorManagerDefault() throws Exception {
+ new MockUp<CdiUtil>() {
+ @Mock
+ @SuppressWarnings("UnusedDeclaration")
+ <T> T lookupService(final Class<T> clazz) {
+ return null;
+ }
+ };
+
+ assertThat(CdiUtil.createHk2InjectionManagerStore(), instanceOf(SingleInjectionManagerStore.class));
+ }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/MyService.java b/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/MyService.java
new file mode 100644
index 0000000..be45c15
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/MyService.java
@@ -0,0 +1,23 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+/**
+ * @author Michal Gajdos
+ */
+public interface MyService {
+}
diff --git a/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.MyService b/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.MyService
new file mode 100644
index 0000000..fd1f187
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.MyService
@@ -0,0 +1,3 @@
+org.glassfish.jersey.ext.cdi1x.internal.CdiUtilTest$MyServiceOne
+org.glassfish.jersey.ext.cdi1x.internal.CdiUtilTest$MyServiceTwo
+org.glassfish.jersey.ext.cdi1x.internal.CdiUtilTest$MyServiceThree
diff --git a/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.BeanManagerProvider b/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.BeanManagerProvider
new file mode 100644
index 0000000..542eea7
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.BeanManagerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.internal.CdiUtilTest$TestBeanManagerProvider
diff --git a/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore b/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore
new file mode 100644
index 0000000..ee3756d
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.internal.CdiUtilTest$TestInjectionManagerStore
diff --git a/ext/cdi/jersey-weld2-se/pom.xml b/ext/cdi/jersey-weld2-se/pom.xml
new file mode 100644
index 0000000..f9e88c8
--- /dev/null
+++ b/ext/cdi/jersey-weld2-se/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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.ext.cdi</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-weld2-se</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-ext-weld2-se</name>
+
+ <description>WELD 2.x SE support</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.weld.se</groupId>
+ <artifactId>weld-se-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext.cdi</groupId>
+ <artifactId>jersey-cdi1x</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/java</directory>
+ <includes>
+ <include>META-INF/**/*</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>${basedir}/src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </build>
+</project>
diff --git a/ext/cdi/jersey-weld2-se/src/main/java/org/glassfish/jersey/weld/se/WeldInjectionManagerStore.java b/ext/cdi/jersey-weld2-se/src/main/java/org/glassfish/jersey/weld/se/WeldInjectionManagerStore.java
new file mode 100644
index 0000000..38f501d
--- /dev/null
+++ b/ext/cdi/jersey-weld2-se/src/main/java/org/glassfish/jersey/weld/se/WeldInjectionManagerStore.java
@@ -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
+ */
+
+package org.glassfish.jersey.weld.se;
+
+import org.glassfish.jersey.ext.cdi1x.internal.GenericInjectionManagerStore;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * {@link InjectionManagerStore Injection manager} for Weld SE container. The provider
+ * enables multiple Jersey applications to be deployed within a single HTTP container.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @since 2.20
+ */
+public class WeldInjectionManagerStore extends GenericInjectionManagerStore {
+
+ @Override
+ public InjectionManager lookupInjectionManager() {
+ return WeldRequestScope.actualInjectorManager.get();
+ }
+}
diff --git a/ext/cdi/jersey-weld2-se/src/main/java/org/glassfish/jersey/weld/se/WeldRequestScope.java b/ext/cdi/jersey-weld2-se/src/main/java/org/glassfish/jersey/weld/se/WeldRequestScope.java
new file mode 100644
index 0000000..24f8c52
--- /dev/null
+++ b/ext/cdi/jersey-weld2-se/src/main/java/org/glassfish/jersey/weld/se/WeldRequestScope.java
@@ -0,0 +1,97 @@
+/*
+ * 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.weld.se;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+
+import org.glassfish.jersey.ext.cdi1x.internal.JerseyVetoed;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.server.spi.ExternalRequestContext;
+import org.glassfish.jersey.server.spi.ExternalRequestScope;
+
+import org.jboss.weld.context.bound.BoundRequestContext;
+
+/**
+ * Weld specific request scope to align CDI request context with Jersey.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@ApplicationScoped
+@JerseyVetoed
+public class WeldRequestScope implements ExternalRequestScope<Map<String, Object>> {
+
+ @Inject
+ private BoundRequestContext context;
+
+ private final ThreadLocal<Map<String, Object>> actualMap = new ThreadLocal<>();
+
+ public static final ThreadLocal<InjectionManager> actualInjectorManager = new ThreadLocal<>();
+
+ @Override
+ public ExternalRequestContext<Map<String, Object>> open(InjectionManager injectionManager) {
+ final Map<String, Object> newMap = new ConcurrentHashMap<>();
+ actualMap.set(newMap);
+ context.associate(newMap);
+ context.activate();
+ actualInjectorManager.set(injectionManager);
+ return new ExternalRequestContext<>(newMap);
+ }
+
+ @Override
+ public void resume(final ExternalRequestContext<Map<String, Object>> ctx, InjectionManager injectionManager) {
+ final Map<String, Object> newMap = ctx.getContext();
+ actualInjectorManager.set(injectionManager);
+ actualMap.set(newMap);
+ context.associate(newMap);
+ context.activate();
+ }
+
+ @Override
+ public void suspend(final ExternalRequestContext<Map<String, Object>> ctx, InjectionManager injectionManager) {
+ try {
+ final Map<String, Object> contextMap = actualMap.get();
+ if (contextMap != null) {
+ context.deactivate();
+ context.dissociate(contextMap);
+ }
+ } finally {
+ actualMap.remove();
+ actualInjectorManager.remove();
+ }
+ }
+
+ @Override
+ public void close() {
+ try {
+ final Map<String, Object> contextMap = actualMap.get();
+ if (contextMap != null) {
+ context.invalidate();
+ context.deactivate();
+ context.dissociate(contextMap);
+ } else {
+ context.deactivate();
+ }
+ } finally {
+ actualMap.remove();
+ actualInjectorManager.remove();
+ }
+ }
+}
diff --git a/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/beans.xml b/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000..aaf0547
--- /dev/null
+++ b/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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
+
+-->
+
+<beans/>
diff --git a/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore b/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore
new file mode 100644
index 0000000..68dc5f1
--- /dev/null
+++ b/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore
@@ -0,0 +1 @@
+org.glassfish.jersey.weld.se.WeldInjectionManagerStore
diff --git a/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ExternalRequestScope b/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ExternalRequestScope
new file mode 100644
index 0000000..bc04ec2
--- /dev/null
+++ b/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ExternalRequestScope
@@ -0,0 +1 @@
+org.glassfish.jersey.weld.se.WeldRequestScope
\ No newline at end of file
diff --git a/ext/cdi/pom.xml b/ext/cdi/pom.xml
new file mode 100644
index 0000000..a3c8254
--- /dev/null
+++ b/ext/cdi/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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.ext</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.ext.cdi</groupId>
+ <artifactId>project</artifactId>
+ <packaging>pom</packaging>
+ <name>jersey-cdi-support</name>
+
+ <description>Jersey CDI providers umbrella project module</description>
+
+ <modules>
+ <module>jersey-cdi1x</module>
+ <module>jersey-cdi1x-ban-custom-hk2-binding</module>
+ <module>jersey-cdi1x-servlet</module>
+ <module>jersey-cdi1x-transaction</module>
+ <module>jersey-cdi1x-validation</module>
+ <module>jersey-weld2-se</module>
+ </modules>
+
+</project>
diff --git a/ext/entity-filtering/pom.xml b/ext/entity-filtering/pom.xml
new file mode 100644
index 0000000..a5619c5
--- /dev/null
+++ b/ext/entity-filtering/pom.xml
@@ -0,0 +1,86 @@
+<?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.ext</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-entity-filtering</artifactId>
+ <name>jersey-ext-entity-filtering</name>
+
+ <description>
+ Jersey extension module providing support for Entity Data Filtering.
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</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>
+ </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>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>org.glassfish.jersey.message.filtering.*;version=${project.version}</Export-Package>
+ <Import-Package>*</Import-Package>
+ </instructions>
+ <unpackBundle>true</unpackBundle>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/CommonScopeProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/CommonScopeProvider.java
new file mode 100644
index 0000000..fb1bbe8
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/CommonScopeProvider.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Spliterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import javax.ws.rs.core.Configuration;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.message.filtering.internal.LocalizationMessages;
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+import org.glassfish.jersey.message.filtering.spi.ScopeProvider;
+import org.glassfish.jersey.message.filtering.spi.ScopeResolver;
+import org.glassfish.jersey.model.internal.RankedComparator;
+
+/**
+ * Default implementation of {@link ScopeProvider scope provider}. This class can be used on client to retrieve
+ * entity-filtering scopes from given entity annotations or injected {@link Configuration configuration}. Class can also serve
+ * as a base class for server-side implementations.
+ *
+ * @author Michal Gajdos
+ * @see ServerScopeProvider
+ */
+@Singleton
+class CommonScopeProvider implements ScopeProvider {
+
+ private static final Logger LOGGER = Logger.getLogger(CommonScopeProvider.class.getName());
+
+ private final List<ScopeResolver> resolvers;
+ private final Configuration config;
+
+ /**
+ * Create new common scope provider with injected {@link Configuration configuration} and
+ * {@link InjectionManager injection manager}.
+ */
+ @Inject
+ public CommonScopeProvider(final Configuration config, final InjectionManager injectionManager) {
+ this.config = config;
+ Spliterator<ScopeResolver> resolverSpliterator =
+ Providers.getAllProviders(injectionManager, ScopeResolver.class, new RankedComparator<>()).spliterator();
+ this.resolvers = StreamSupport.stream(resolverSpliterator, false).collect(Collectors.toList());
+ }
+
+ @Override
+ public Set<String> getFilteringScopes(final Annotation[] entityAnnotations, final boolean defaultIfNotFound) {
+ Set<String> filteringScopes = new HashSet<>();
+
+ // Entity Annotations.
+ filteringScopes.addAll(getFilteringScopes(entityAnnotations));
+
+ if (filteringScopes.isEmpty()) {
+ // Configuration.
+ filteringScopes.addAll(getFilteringScopes(config));
+ }
+
+ // Use default scope if not in other scope.
+ return returnFilteringScopes(filteringScopes, defaultIfNotFound);
+ }
+
+ /**
+ * Return the default entity-filtering scope if the given set of scopes is empty and the processing should fallback to the
+ * default.
+ *
+ * @param filteringScopes entity-filtering scopes to be examined.
+ * @param returnDefaultFallback {@code true} if the default entity-filtering scope should be returned if the given scopes
+ * are empty, {@code false} otherwise.
+ * @return entity-filtering scopes.
+ */
+ protected Set<String> returnFilteringScopes(final Set<String> filteringScopes, final boolean returnDefaultFallback) {
+ return returnDefaultFallback && filteringScopes.isEmpty() ? FilteringHelper.getDefaultFilteringScope() : filteringScopes;
+ }
+
+ /**
+ * Get entity-filtering scopes from all available {@link ScopeResolver scope resolvers} for given annotations.
+ *
+ * @param annotations annotations to retrieve entity-filtering scopes from.
+ * @return entity-filtering scopes or an empty set if none scope can be resolved.
+ */
+ protected Set<String> getFilteringScopes(final Annotation[] annotations) {
+ Set<String> filteringScopes = new HashSet<>();
+ for (final ScopeResolver provider : resolvers) {
+ mergeFilteringScopes(filteringScopes, provider.resolve(annotations));
+ }
+ return filteringScopes;
+ }
+
+ /**
+ * Get entity-filtering scopes from {@link Configuration}.
+ *
+ * @param config configuration the entity-filtering scopes are obtained from.
+ * @return entity-filtering scopes or an empty set if none scope can be resolved.
+ */
+ private Set<String> getFilteringScopes(final Configuration config) {
+ final Object property = config.getProperty(EntityFilteringFeature.ENTITY_FILTERING_SCOPE);
+
+ Set<String> filteringScopes = Collections.emptySet();
+ if (property != null) {
+ if (property instanceof Annotation) {
+ filteringScopes = getFilteringScopes(new Annotation[] {(Annotation) property});
+ } else if (property instanceof Annotation[]) {
+ filteringScopes = getFilteringScopes((Annotation[]) property);
+ } else {
+ LOGGER.log(Level.CONFIG, LocalizationMessages.ENTITY_FILTERING_SCOPE_NOT_ANNOTATIONS(property));
+ }
+ }
+ return filteringScopes;
+ }
+
+ /**
+ * Merge two sets of entity-filtering scopes.
+ *
+ * @param filteringScopes existing entity-filtering scopes.
+ * @param resolvedScopes entity-filtering scopes to be added to the existing ones.
+ */
+ protected void mergeFilteringScopes(final Set<String> filteringScopes, final Set<String> resolvedScopes) {
+ if (!filteringScopes.isEmpty() && !resolvedScopes.isEmpty()) {
+ LOGGER.log(Level.FINE, LocalizationMessages.MERGING_FILTERING_SCOPES());
+ }
+
+ filteringScopes.addAll(resolvedScopes);
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/DefaultEntityProcessor.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/DefaultEntityProcessor.java
new file mode 100644
index 0000000..09ba981
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/DefaultEntityProcessor.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+
+import javax.annotation.Priority;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.message.filtering.spi.AbstractEntityProcessor;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessorContext;
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+
+/**
+ * Default entity processor. Handles unannotated properties/accessors and adds them into default entity-filtering scope.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+@Priority(Integer.MAX_VALUE - 1000)
+final class DefaultEntityProcessor extends AbstractEntityProcessor {
+
+ @Override
+ public Result process(final EntityProcessorContext context) {
+ switch (context.getType()) {
+ case CLASS_READER:
+ case CLASS_WRITER:
+ final EntityGraph graph = context.getEntityGraph();
+ if (graph.getFilteringScopes().isEmpty()) {
+ graph.addFilteringScopes(FilteringHelper.getDefaultFilteringScope());
+ }
+ return Result.APPLY;
+
+ case PROPERTY_READER:
+ case PROPERTY_WRITER:
+ final Field field = context.getField();
+ process(context.getEntityGraph(), field.getName(), field.getGenericType());
+ return Result.APPLY;
+
+ case METHOD_READER:
+ case METHOD_WRITER:
+ final Method method = context.getMethod();
+ process(context.getEntityGraph(), ReflectionHelper.getPropertyName(method), method.getGenericReturnType());
+ return Result.APPLY;
+
+ default:
+ // NOOP.
+ }
+ return Result.SKIP;
+ }
+
+ private void process(final EntityGraph graph, final String fieldName, final Type fieldType) {
+ if (!graph.presentInScopes(fieldName)) {
+ addFilteringScopes(fieldName, FilteringHelper.getEntityClass(fieldType), graph.getClassFilteringScopes(), graph);
+ }
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EmptyEntityGraphImpl.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EmptyEntityGraphImpl.java
new file mode 100644
index 0000000..59d074b
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EmptyEntityGraphImpl.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+
+/**
+ * {@link EntityGraph} implementation that does not contain any fields/subgraphs. Methods that are supposed to modify the graph
+ * would throw an {@link UnsupportedOperationException}.
+ *
+ * @author Michal Gajdos
+ */
+final class EmptyEntityGraphImpl implements EntityGraph {
+
+ private final Class<?> clazz;
+
+ @SuppressWarnings("JavaDoc")
+ EmptyEntityGraphImpl(final Class<?> clazz) {
+ this.clazz = clazz;
+ }
+
+ @Override
+ public EntityGraph addField(final String fieldName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public EntityGraph addField(final String fieldName, final String... filteringScopes) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public EntityGraph addField(final String fieldName, final Set<String> filteringScopes) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public EntityGraph addSubgraph(final String fieldName, final Class<?> fieldClass) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public EntityGraph addSubgraph(final String fieldName, final Class<?> fieldClass, final String... filteringScopes) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public EntityGraph addSubgraph(final String fieldName, final Class<?> fieldClass, final Set<String> filteringScopes) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Class<?> getEntityClass() {
+ return clazz;
+ }
+
+ @Override
+ public Set<String> getFields(final String filteringScope) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> getFields(final String... filteringScopes) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> getFields(final Set<String> filteringScopes) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Map<String, Class<?>> getSubgraphs(final String filteringScope) {
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public Map<String, Class<?>> getSubgraphs(final String... filteringScopes) {
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public Map<String, Class<?>> getSubgraphs(final Set<String> filteringScopes) {
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public boolean presentInScopes(final String field) {
+ return false;
+ }
+
+ @Override
+ public boolean presentInScope(final String field, String filteringScope) {
+ return false;
+ }
+
+ @Override
+ public EntityGraph remove(final String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Set<String> getFilteringScopes() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> getClassFilteringScopes() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public EntityGraph addFilteringScopes(final Set<String> filteringScopes) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final EmptyEntityGraphImpl that = (EmptyEntityGraphImpl) o;
+
+ return clazz.equals(that.clazz);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return clazz.hashCode();
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EmptyObjectGraph.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EmptyObjectGraph.java
new file mode 100644
index 0000000..5e7864e
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EmptyObjectGraph.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.glassfish.jersey.message.filtering.spi.ObjectGraph;
+
+/**
+ * Object graph representing empty domain classes.
+ *
+ * @author Michal Gajdos
+ */
+final class EmptyObjectGraph implements ObjectGraph {
+
+ private final Class<?> entityClass;
+
+ EmptyObjectGraph(final Class<?> entityClass) {
+ this.entityClass = entityClass;
+ }
+
+ @Override
+ public Class<?> getEntityClass() {
+ return entityClass;
+ }
+
+ @Override
+ public Set<String> getFields() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> getFields(final String parent) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Map<String, ObjectGraph> getSubgraphs() {
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public Map<String, ObjectGraph> getSubgraphs(final String parent) {
+ return Collections.emptyMap();
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFiltering.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFiltering.java
new file mode 100644
index 0000000..98d7d7d
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFiltering.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Meta-annotation used to create entity filtering annotations for entity (model) classes and resource methods and resources.
+ * <p>
+ * Entity Data Filtering via annotations is supposed to be used to annotate:
+ * <ul>
+ * <li>entity classes (supported on both, server and client sides), and</li>
+ * <li>resource methods / resource classes (server side)</li>
+ * </ul>
+ * </p>
+ * <p>
+ * In entity filtering, a <i>entity-filtering</i> annotation is first defined using the {@code @EntityFiltering} meta-annotation:
+ * <pre>
+ * @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
+ * @Retention(value = RetentionPolicy.RUNTIME)
+ * <b>@EntityFiltering</b>
+ * <b>public @interface DetailedView</b> {
+ *
+ * public static class Factory extends <b>AnnotationLiteral<DetailedView></b> implements <b>DetailedView</b> {
+ *
+ * public static <b>DetailedView</b> get() {
+ return new Factory();
+ }
+ * }
+ * }
+ * </pre>
+ * </p>
+ * <p>
+ * Entity-filtering annotation should provide a factory class/method to create an instance of the annotation. Example of such
+ * factory can be seen in the {@code DetailedView} above. Such instances can be then passed to the client/server runtime to
+ * define/override entity-filtering scopes.
+ * </p>
+ * <p>
+ * The defined entity-filtering annotation is then used to decorate a entity, it's property accessors or fields (more than one
+ * entity may be decorated with the same entity-filtering annotation):
+ * <pre>
+ * public class MyEntityClass {
+ *
+ * <b>@DetailedView</b>
+ * private String myField;
+ *
+ * ...
+ * }
+ * </pre>
+ * </p>
+ * <p>
+ * At last, on the server-side, the entity-filtering annotations are applied to the resource or resource method(s) to which the
+ * entity-filtering should be applied:
+ * <pre>
+ * @Path("/")
+ * public class MyResourceClass {
+ *
+ * @GET
+ * @Produces("text/plain")
+ * @Path("{id}")
+ * <b>@DetailedView</b>
+ * public MyEntityClass get(@PathParam("id") String id) {
+ * // Return MyEntityClass.
+ * }
+ * }
+ * </pre>
+ * </p>
+ * <p>
+ * At last, on the client-side, the entity-filtering annotations are passed to the runtime via
+ * {@link javax.ws.rs.client.Entity#entity(Object, javax.ws.rs.core.MediaType, java.lang.annotation.Annotation[]) Entity.entity()}
+ * method and the entity-filtering scopes are then derived from the annotations:
+ * <pre>
+ * ClientBuilder.newClient()
+ * .target("resource")
+ * .request()
+ * .post(Entity.entity(myentity, "application/json", <b>new Annotation[] {MyEntityClass.Factory.get()}</b>));
+ * </pre>
+ * </p>
+ *
+ * @author Michal Gajdos
+ */
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface EntityFiltering {
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringBinder.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringBinder.java
new file mode 100644
index 0000000..8990270
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringBinder.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering;
+
+import javax.ws.rs.core.GenericType;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.message.filtering.spi.EntityGraphProvider;
+import org.glassfish.jersey.message.filtering.spi.EntityInspector;
+import org.glassfish.jersey.message.filtering.spi.ObjectGraph;
+import org.glassfish.jersey.message.filtering.spi.ObjectGraphTransformer;
+import org.glassfish.jersey.message.filtering.spi.ObjectProvider;
+
+/**
+ * Binder for Entity Data Filtering feature.
+ *
+ * @author Michal Gajdos
+ */
+final class EntityFilteringBinder extends AbstractBinder {
+
+ @Override
+ protected void configure() {
+ // Entity Inspector.
+ bind(EntityInspectorImpl.class)
+ .to(EntityInspector.class)
+ .in(Singleton.class);
+
+ // Entity Graph Provider.
+ bind(EntityGraphProviderImpl.class)
+ .to(EntityGraphProvider.class)
+ .in(Singleton.class);
+
+ // Object Provider & Object Graph Transformer.
+ bindAsContract(ObjectGraphProvider.class)
+ // FilteringObjectProvider.
+ .to(ObjectProvider.class)
+ .to(new GenericType<ObjectProvider<Object>>() {})
+ .to(new GenericType<ObjectProvider<ObjectGraph>>() {})
+ // FilteringGraphTransformer.
+ .to(ObjectGraphTransformer.class)
+ .to(new GenericType<ObjectGraphTransformer<Object>>() {})
+ .to(new GenericType<ObjectGraphTransformer<ObjectGraph>>() {})
+ // Scope.
+ .in(Singleton.class);
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringFeature.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringFeature.java
new file mode 100644
index 0000000..4c4e1eb
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringFeature.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+/**
+ * {@link Feature} used to add support for Entity Data Filtering feature for entity-filtering annotations based on
+ * {@link EntityFiltering} meta-annotation.
+ *
+ * @author Michal Gajdos
+ */
+public final class EntityFilteringFeature implements Feature {
+
+ /**
+ * Defines one or more annotations that should be used as entity-filtering scope when reading/writing an entity.
+ * <p>
+ * The property can be used on client to define the scope as well as on server to override the scope derived from current
+ * request processing context (resource methods / resource classes).
+ * </p>
+ * <p>
+ * If the property is set, the specified annotations will be used to create (override) entity-filtering scope.
+ * </p>
+ * <p>
+ * The property value MUST be an instance of {@link java.lang.annotation.Annotation} or {@code Annotation[]} array. To obtain
+ * the annotation instances refer to the {@link EntityFiltering} for requirements on creating entity-filtering annotations.
+ * </p>
+ * <p>
+ * A default value is not set.
+ * </p>
+ * <p>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ * </p>
+ *
+ * @see EntityFiltering
+ */
+ public static final String ENTITY_FILTERING_SCOPE = "jersey.config.entityFiltering.scope";
+
+ @Override
+ public boolean configure(final FeatureContext context) {
+ final Configuration config = context.getConfiguration();
+
+ if (!config.isRegistered(EntityFilteringProcessor.class)) {
+ // Binder (FilteringObjectProvider/FilteringGraphTransformer).
+ if (!config.isRegistered(EntityFilteringBinder.class)) {
+ context.register(new EntityFilteringBinder());
+ }
+
+ // Entity Processors.
+ context.register(EntityFilteringProcessor.class);
+ if (!config.isRegistered(DefaultEntityProcessor.class)) {
+ context.register(DefaultEntityProcessor.class);
+ }
+
+ // Scope Providers.
+ context.register(EntityFilteringScopeResolver.class);
+
+ // Scope Resolver.
+ if (RuntimeType.SERVER == config.getRuntimeType()) {
+ context.register(ServerScopeProvider.class);
+ } else {
+ context.register(CommonScopeProvider.class);
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return {@code true} whether at least one of the entity filtering features is registered in the given config.
+ *
+ * @param config config to be examined for presence of entity filtering feature.
+ * @return {@code true} if entity filtering is enabled for given config, {@code false} otherwise.
+ */
+ public static boolean enabled(final Configuration config) {
+ return config.isRegistered(EntityFilteringFeature.class)
+ || config.isRegistered(SecurityEntityFilteringFeature.class)
+ || config.isRegistered(SelectableEntityFilteringFeature.class);
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringHelper.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringHelper.java
new file mode 100644
index 0000000..2dc188d
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringHelper.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+
+/**
+ * Utility methods for Entity Data Filtering.
+ *
+ * @author Michal Gajdos
+ */
+final class EntityFilteringHelper {
+
+ /**
+ * Get entity-filtering scopes from given annotations. Scopes are only derived from entity-filtering annotations.
+ *
+ * @param annotations list of arbitrary annotations.
+ * @return a set of entity-filtering scopes.
+ */
+ public static Set<String> getFilteringScopes(final Annotation[] annotations) {
+ return getFilteringScopes(annotations, true);
+ }
+
+ /**
+ * Get entity-filtering scopes from given annotations. Scopes are only derived from entity-filtering annotations.
+ *
+ * @param annotations list of arbitrary annotations.
+ * @param filter {@code true} whether the given annotation should be reduced to only entity-filtering annotations,
+ * {@code false} otherwise.
+ * @return a set of entity-filtering scopes.
+ */
+ public static Set<String> getFilteringScopes(Annotation[] annotations, final boolean filter) {
+ if (annotations.length == 0) {
+ return Collections.emptySet();
+ }
+
+ final Set<String> contexts = new HashSet<>(annotations.length);
+
+ annotations = filter ? getFilteringAnnotations(annotations) : annotations;
+ for (final Annotation annotation : annotations) {
+ contexts.add(annotation.annotationType().getName());
+ }
+
+ return contexts;
+ }
+
+ /**
+ * Filter given annotations and return only entity-filtering ones.
+ *
+ * @param annotations list of arbitrary annotations.
+ * @return entity-filtering annotations or an empty array.
+ */
+ public static Annotation[] getFilteringAnnotations(final Annotation[] annotations) {
+ if (annotations == null || annotations.length == 0) {
+ return FilteringHelper.EMPTY_ANNOTATIONS;
+ }
+
+ final List<Annotation> filteringAnnotations = new ArrayList<>(annotations.length);
+
+ for (final Annotation annotation : annotations) {
+ final Class<? extends Annotation> annotationType = annotation.annotationType();
+
+ for (final Annotation metaAnnotation : annotationType.getDeclaredAnnotations()) {
+ if (metaAnnotation instanceof EntityFiltering) {
+ filteringAnnotations.add(annotation);
+ }
+ }
+ }
+
+ return filteringAnnotations.toArray(new Annotation[filteringAnnotations.size()]);
+ }
+
+ public static <T extends Annotation> T getAnnotation(final Annotation[] annotations, final Class<T> clazz) {
+ for (final Annotation annotation : annotations) {
+ if (annotation.annotationType().getClass().isAssignableFrom(clazz)) {
+ //noinspection unchecked
+ return (T) annotation;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Prevent instantiation.
+ */
+ private EntityFilteringHelper() {
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringProcessor.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringProcessor.java
new file mode 100644
index 0000000..4af5311
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringProcessor.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Priority;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.message.filtering.spi.AbstractEntityProcessor;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessorContext;
+
+/**
+ * Entity processor handling entity-filtering annotations.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+@Priority(Integer.MAX_VALUE - 2000)
+final class EntityFilteringProcessor extends AbstractEntityProcessor {
+
+ @Override
+ public Result process(final EntityProcessorContext context) {
+ switch (context.getType()) {
+ case CLASS_READER:
+ case CLASS_WRITER:
+ addGlobalScopes(EntityFilteringHelper.getFilteringScopes(context.getEntityClass().getDeclaredAnnotations()),
+ context.getEntityGraph());
+ break;
+ default:
+ // NOOP.
+ break;
+ }
+ return super.process(context);
+ }
+
+ @Override
+ protected Result process(final String field, final Class<?> fieldClass, final Annotation[] fieldAnnotations,
+ final Annotation[] annotations, final EntityGraph graph) {
+ final Set<String> filteringScopes = new HashSet<>();
+
+ if (fieldAnnotations.length > 0) {
+ filteringScopes.addAll(EntityFilteringHelper.getFilteringScopes(fieldAnnotations));
+ }
+ if (annotations.length > 0) {
+ filteringScopes.addAll(EntityFilteringHelper.getFilteringScopes(annotations));
+ }
+
+ if (!filteringScopes.isEmpty()) {
+ if (field != null) {
+ addFilteringScopes(field, fieldClass, filteringScopes, graph);
+ } else {
+ addGlobalScopes(filteringScopes, graph);
+ }
+ return Result.APPLY;
+ } else {
+ return Result.SKIP;
+ }
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringScopeResolver.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringScopeResolver.java
new file mode 100644
index 0000000..cd71ec8
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringScopeResolver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.message.filtering.spi.ScopeResolver;
+
+/**
+ * {@link ScopeResolver Scope provider} processing entity-filtering annotations created using
+ * {@link org.glassfish.jersey.message.filtering.EntityFiltering @EntityFiltering} meta-annotation.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+final class EntityFilteringScopeResolver implements ScopeResolver {
+
+ @Override
+ public Set<String> resolve(final Annotation[] annotations) {
+ return EntityFilteringHelper.getFilteringScopes(annotations);
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityGraphImpl.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityGraphImpl.java
new file mode 100644
index 0000000..3d4962c
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityGraphImpl.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.glassfish.jersey.internal.guava.HashBasedTable;
+import org.glassfish.jersey.internal.guava.HashMultimap;
+import org.glassfish.jersey.internal.guava.Table;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.ScopeProvider;
+
+/**
+ * Default implementation of {@link EntityGraph}.
+ *
+ * @author Michal Gajdos
+ */
+final class EntityGraphImpl implements EntityGraph {
+
+ private final Class<?> entityClass;
+
+ private final Set<String> globalScopes;
+ private final Set<String> localScopes;
+
+ // <FilteringScope, FieldName>
+ private final HashMultimap<String, String> fields;
+ // <FilteringScope, FieldName, Class>
+ private final Table<String, String, Class<?>> subgraphs;
+
+ /**
+ * Create an entity graph for given class.
+ *
+ * @param entityClass entity class the graph should be created for.
+ */
+ public EntityGraphImpl(final Class<?> entityClass) {
+ this.entityClass = entityClass;
+
+ this.fields = HashMultimap.create();
+ this.subgraphs = HashBasedTable.create();
+
+ this.globalScopes = new HashSet<>();
+ this.localScopes = new HashSet<>();
+ }
+
+ @Override
+ public EntityGraphImpl addField(final String fieldName) {
+ return addField(fieldName, globalScopes);
+ }
+
+ @Override
+ public EntityGraphImpl addField(final String fieldName, final String... filteringScopes) {
+ return addField(fieldName, Arrays.stream(filteringScopes).collect(Collectors.toSet()));
+ }
+
+ @Override
+ public EntityGraphImpl addField(final String fieldName, final Set<String> filteringScopes) {
+ for (final String filteringScope : filteringScopes) {
+ createFilteringScope(filteringScope);
+ fields.get(filteringScope).add(fieldName);
+ }
+
+ return this;
+ }
+
+ @Override
+ public EntityGraphImpl addFilteringScopes(final Set<String> filteringScopes) {
+ this.globalScopes.addAll(filteringScopes);
+ return this;
+ }
+
+ @Override
+ public EntityGraphImpl addSubgraph(final String fieldName, final Class<?> fieldClass) {
+ return addSubgraph(fieldName, fieldClass, globalScopes);
+ }
+
+ @Override
+ public EntityGraphImpl addSubgraph(final String fieldName, final Class<?> fieldClass, final String... filteringScopes) {
+ return addSubgraph(fieldName, fieldClass, Arrays.stream(filteringScopes).collect(Collectors.toSet()));
+ }
+
+ @Override
+ public EntityGraphImpl addSubgraph(final String fieldName, final Class<?> fieldClass, final Set<String> filteringScopes) {
+ for (final String filteringScope : filteringScopes) {
+ createFilteringScope(filteringScope);
+ subgraphs.put(filteringScope, fieldName, fieldClass);
+ }
+
+ return this;
+ }
+
+ @Override
+ public Class<?> getEntityClass() {
+ return entityClass;
+ }
+
+ @Override
+ public Set<String> getFields(final String filteringScope) {
+ return fields.containsKey(filteringScope)
+ ? Collections.unmodifiableSet(fields.get(filteringScope)) : Collections.<String>emptySet();
+ }
+
+ @Override
+ public Set<String> getFields(final String... filteringScopes) {
+ return filteringScopes.length == 0 ? Collections.<String>emptySet()
+ : (filteringScopes.length == 1
+ ? getFields(filteringScopes[0])
+ : getFields(Arrays.stream(filteringScopes).collect(Collectors.toSet())));
+ }
+
+ @Override
+ public Set<String> getFields(final Set<String> filteringScopes) {
+ final Set<String> matched = new HashSet<>();
+
+ for (final String filteringContext : filteringScopes) {
+ matched.addAll(fields.get(filteringContext));
+ }
+
+ return matched;
+ }
+
+ @Override
+ public Set<String> getFilteringScopes() {
+ HashSet<String> strings = new HashSet<>(globalScopes);
+ strings.addAll(localScopes);
+ return Collections.unmodifiableSet(strings);
+ }
+
+ @Override
+ public Set<String> getClassFilteringScopes() {
+ return Collections.unmodifiableSet(globalScopes);
+ }
+
+ @Override
+ public Map<String, Class<?>> getSubgraphs(final String filteringScope) {
+ return subgraphs.containsRow(filteringScope)
+ ? Collections.unmodifiableMap(subgraphs.row(filteringScope)) : Collections.<String, Class<?>>emptyMap();
+ }
+
+ @Override
+ public Map<String, Class<?>> getSubgraphs(final String... filteringScopes) {
+ return filteringScopes.length == 0
+ ? Collections.<String, Class<?>>emptyMap()
+ : (filteringScopes.length == 1
+ ? getSubgraphs(filteringScopes[0])
+ : getSubgraphs(Arrays.stream(filteringScopes).collect(Collectors.toSet())));
+ }
+
+ @Override
+ public Map<String, Class<?>> getSubgraphs(final Set<String> filteringScopes) {
+ final Map<String, Class<?>> matched = new HashMap<>();
+
+ for (final String filteringContext : filteringScopes) {
+ matched.putAll(subgraphs.row(filteringContext));
+ }
+
+ return matched;
+ }
+
+ @Override
+ public boolean presentInScopes(final String name) {
+ return fields.containsValue(name) || subgraphs.containsColumn(name);
+ }
+
+ @Override
+ public boolean presentInScope(final String field, final String filteringScope) {
+ return fields.containsEntry(filteringScope, field) || subgraphs.contains(filteringScope, field);
+ }
+
+ @Override
+ public EntityGraphImpl remove(final String fieldName) {
+ for (final String scope : getFilteringScopes()) {
+ if (fields.containsEntry(scope, fieldName)) {
+ fields.remove(scope, fieldName);
+ }
+ if (subgraphs.containsColumn(fieldName)) {
+ subgraphs.remove(scope, fieldName);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Create a new entity-filtering scope based on the {@link ScopeProvider#DEFAULT_SCOPE default one}.
+ *
+ * @param filteringScope entity-filtering scope to be created.
+ */
+ private void createFilteringScope(final String filteringScope) {
+ // Do not create a scope if it already exists.
+ if (!getFilteringScopes().contains(filteringScope)) {
+ // Copy contents of default scope into the new one.
+ if (localScopes.contains(ScopeProvider.DEFAULT_SCOPE)) {
+ fields.putAll(filteringScope, fields.get(ScopeProvider.DEFAULT_SCOPE));
+
+ final Map<String, Class<?>> row = subgraphs.row(ScopeProvider.DEFAULT_SCOPE);
+ for (final Map.Entry<String, Class<?>> entry : row.entrySet()) {
+ subgraphs.put(filteringScope, entry.getKey(), entry.getValue());
+ }
+ }
+ localScopes.add(filteringScope);
+ }
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final EntityGraphImpl that = (EntityGraphImpl) o;
+
+ return entityClass.equals(that.entityClass)
+ && fields.equals(that.fields)
+ && globalScopes.equals(that.globalScopes)
+ && localScopes.equals(that.localScopes)
+ && subgraphs.equals(that.subgraphs);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = entityClass.hashCode();
+ result = 53 * result + globalScopes.hashCode();
+ result = 53 * result + localScopes.hashCode();
+ result = 53 * result + fields.hashCode();
+ result = 53 * result + subgraphs.hashCode();
+ return result;
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityGraphProviderImpl.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityGraphProviderImpl.java
new file mode 100644
index 0000000..ec67e9e
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityGraphProviderImpl.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import org.glassfish.jersey.internal.util.collection.DataStructures;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityGraphProvider;
+import org.glassfish.jersey.message.filtering.spi.ObjectGraph;
+
+/**
+ * Provides {@link EntityGraph entity graph} and {@link ObjectGraph object graph} instances.
+ *
+ * @author Michal Gajdos
+ */
+final class EntityGraphProviderImpl implements EntityGraphProvider {
+
+ private final ConcurrentMap<Class<?>, EntityGraph> writerClassToGraph = DataStructures.createConcurrentMap();
+ private final ConcurrentMap<Class<?>, EntityGraph> readerClassToGraph = DataStructures.createConcurrentMap();
+
+ @Override
+ public EntityGraph getOrCreateEntityGraph(final Class<?> entityClass, final boolean forWriter) {
+ final ConcurrentMap<Class<?>, EntityGraph> classToGraph = forWriter ? writerClassToGraph : readerClassToGraph;
+
+ if (!classToGraph.containsKey(entityClass)) {
+ classToGraph.putIfAbsent(entityClass, new EntityGraphImpl(entityClass));
+ }
+ return classToGraph.get(entityClass);
+ }
+
+ @Override
+ public EntityGraph getOrCreateEmptyEntityGraph(final Class<?> entityClass, final boolean forWriter) {
+ final ConcurrentMap<Class<?>, EntityGraph> classToGraph = forWriter ? writerClassToGraph : readerClassToGraph;
+
+ if (!classToGraph.containsKey(entityClass)
+ || !(classToGraph.get(entityClass) instanceof EmptyEntityGraphImpl)) {
+ classToGraph.put(entityClass, new EmptyEntityGraphImpl(entityClass));
+ }
+ return classToGraph.get(entityClass);
+ }
+
+ /**
+ * Return an unmodifiable map of entity graphs for reader/writer.
+ *
+ * @param forWriter flag determining whether the returned map should be for writer/reader.
+ * @return an unmodifiable map of entity graphs.
+ */
+ public Map<Class<?>, EntityGraph> asMap(final boolean forWriter) {
+ return Collections.unmodifiableMap(forWriter ? writerClassToGraph : readerClassToGraph);
+ }
+
+ @Override
+ public boolean containsEntityGraph(final Class<?> entityClass, final boolean forWriter) {
+ return forWriter ? writerClassToGraph.containsKey(entityClass) : readerClassToGraph.containsKey(entityClass);
+ }
+
+ @Override
+ public ObjectGraph createObjectGraph(final Class<?> entityClass, final Set<String> filteringScopes,
+ final boolean forWriter) {
+ final Map<Class<?>, EntityGraph> classToGraph = forWriter ? writerClassToGraph : readerClassToGraph;
+ final EntityGraph entityGraph = classToGraph.get(entityClass);
+
+ return entityGraph == null
+ ? new EmptyObjectGraph(entityClass)
+ : new ObjectGraphImpl(classToGraph, entityGraph, filteringScopes);
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityInspectorImpl.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityInspectorImpl.java
new file mode 100644
index 0000000..76af833
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityInspectorImpl.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Spliterator;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityGraphProvider;
+import org.glassfish.jersey.message.filtering.spi.EntityInspector;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessor;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessorContext;
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+import org.glassfish.jersey.model.internal.RankedComparator;
+
+/**
+ * Class responsible for inspecting entity classes. This class invokes all available {@link EntityProcessor entity processors} in
+ * different {@link EntityProcessorContext contexts}.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+final class EntityInspectorImpl implements EntityInspector {
+
+ private final List<EntityProcessor> entityProcessors;
+
+ @Inject
+ private EntityGraphProvider graphProvider;
+
+ /**
+ * Constructor expecting {@link InjectionManager} to be injected.
+ *
+ * @param injectionManager injection manager to be injected.
+ */
+ @Inject
+ public EntityInspectorImpl(final InjectionManager injectionManager) {
+ Spliterator<EntityProcessor> entities =
+ Providers.getAllProviders(injectionManager, EntityProcessor.class, new RankedComparator<>()).spliterator();
+ this.entityProcessors = StreamSupport.stream(entities, false).collect(Collectors.toList());
+ }
+
+ @Override
+ public void inspect(final Class<?> entityClass, final boolean forWriter) {
+ if (!graphProvider.containsEntityGraph(entityClass, forWriter)) {
+ final EntityGraph graph = graphProvider.getOrCreateEntityGraph(entityClass, forWriter);
+ final Set<Class<?>> inspect = new HashSet<>();
+
+ // Class.
+ if (!inspectEntityClass(entityClass, graph, forWriter)) {
+ // Properties.
+ final Map<String, Method> unmatchedAccessors = inspectEntityProperties(entityClass, graph, inspect, forWriter);
+
+ // Setters/Getters without fields.
+ inspectStandaloneAccessors(unmatchedAccessors, graph, forWriter);
+
+ // Inspect new classes.
+ for (final Class<?> clazz : inspect) {
+ inspect(clazz, forWriter);
+ }
+ }
+ }
+ }
+
+ /**
+ * Invoke available {@link EntityProcessor}s on given entity class.
+ *
+ * @param entityClass entity class to be examined.
+ * @param graph entity graph to be modified by examination.
+ * @param forWriter flag determining whether the class should be examined for reader or writer.
+ * @return {@code true} if the inspecting should be roll-backed, {@code false} otherwise.
+ */
+ private boolean inspectEntityClass(final Class<?> entityClass, final EntityGraph graph, final boolean forWriter) {
+ final EntityProcessorContextImpl context = new EntityProcessorContextImpl(
+ forWriter ? EntityProcessorContext.Type.CLASS_WRITER : EntityProcessorContext.Type.CLASS_READER,
+ entityClass, graph);
+
+ for (final EntityProcessor processor : entityProcessors) {
+ final EntityProcessor.Result result = processor.process(context);
+
+ if (EntityProcessor.Result.ROLLBACK == result) {
+ graphProvider.getOrCreateEmptyEntityGraph(entityClass, false);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Invoke available {@link EntityProcessor}s on fields of given entity class. Method returns a map ({@code fieldName},
+ * {@code method}) of unprocessed property accessors (getters/setters) and fills {@code inspect} set with entity classes
+ * that should be further processed.
+ *
+ * @param entityClass entity class to obtain properties to be examined.
+ * @param graph entity graph to be modified by examination.
+ * @param inspect non-null set of classes to-be-examined.
+ * @param forWriter flag determining whether the class should be examined for reader or writer.
+ * @return map of unprocessed property accessors.
+ */
+ private Map<String, Method> inspectEntityProperties(final Class<?> entityClass, final EntityGraph graph,
+ final Set<Class<?>> inspect, final boolean forWriter) {
+ final Field[] fields = AccessController.doPrivileged(ReflectionHelper.getAllFieldsPA(entityClass));
+ final Map<String, Method> methods = FilteringHelper.getPropertyMethods(entityClass, forWriter);
+
+ for (final Field field : fields) {
+ // Ignore static fields.
+ if (Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+
+ final String name = field.getName();
+ final Class<?> clazz = FilteringHelper.getEntityClass(field.getGenericType());
+ final Method method = methods.remove(name);
+
+ final EntityProcessorContextImpl context = new EntityProcessorContextImpl(
+ forWriter ? EntityProcessorContext.Type.PROPERTY_WRITER : EntityProcessorContext.Type.PROPERTY_READER,
+ field, method, graph);
+
+ boolean rollback = false;
+ for (final EntityProcessor processor : entityProcessors) {
+ final EntityProcessor.Result result = processor.process(context);
+
+ if (EntityProcessor.Result.ROLLBACK == result) {
+ rollback = true;
+ graph.remove(name);
+ break;
+ }
+ }
+
+ if (!rollback && FilteringHelper.filterableEntityClass(clazz)) {
+ inspect.add(clazz);
+ }
+ }
+
+ return methods;
+ }
+
+ /**
+ * Invoke available {@link EntityProcessor}s on accessors (getter/setter) that has no match in classes' fields.
+ *
+ * @param unprocessedAccessors map of unprocessed accessors.
+ * @param graph entity graph to be modified by examination.
+ * @param forWriter flag determining whether the class should be examined for reader or writer.
+ */
+ private void inspectStandaloneAccessors(final Map<String, Method> unprocessedAccessors, final EntityGraph graph,
+ final boolean forWriter) {
+ for (final Map.Entry<String, Method> entry : unprocessedAccessors.entrySet()) {
+ final EntityProcessorContextImpl context = new EntityProcessorContextImpl(
+ forWriter ? EntityProcessorContext.Type.METHOD_WRITER : EntityProcessorContext.Type.METHOD_READER,
+ entry.getValue(), graph);
+
+ for (final EntityProcessor processor : entityProcessors) {
+ final EntityProcessor.Result result = processor.process(context);
+
+ if (EntityProcessor.Result.ROLLBACK == result) {
+ graph.remove(entry.getKey());
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityProcessorContextImpl.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityProcessorContextImpl.java
new file mode 100644
index 0000000..a47160e
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityProcessorContextImpl.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessorContext;
+
+/**
+ * Default {@link EntityProcessorContext entity processor context} implementation.
+ *
+ * @author Michal Gajdos
+ */
+final class EntityProcessorContextImpl implements EntityProcessorContext {
+
+ private final Type type;
+
+ private final Class<?> clazz;
+
+ private final Field field;
+
+ private final Method method;
+
+ private final EntityGraph graph;
+
+ /**
+ * Create entity processor context for processing entity classes.
+ *
+ * @param type {@link Type#CLASS_READER} or {@link Type#CLASS_WRITER}.
+ * @param clazz entity class.
+ * @param graph entity-filtering graph associated with entity class.
+ */
+ public EntityProcessorContextImpl(final Type type, final Class<?> clazz, final EntityGraph graph) {
+ this(type, clazz, null, null, graph);
+ }
+
+ /**
+ * Create entity processor context for processing entity properties.
+ *
+ * @param type {@link Type#PROPERTY_READER} or {@link Type#PROPERTY_WRITER}.
+ * @param field entity property field.
+ * @param method entity property accessor.
+ * @param graph entity-filtering graph associated with entity class.
+ */
+ public EntityProcessorContextImpl(final Type type, final Field field, final Method method, final EntityGraph graph) {
+ this(type, null, field, method, graph);
+ }
+
+ /**
+ * Create entity processor context for processing entity accessors.
+ *
+ * @param type {@link Type#METHOD_READER} or {@link Type#METHOD_WRITER}.
+ * @param method entity property accessor.
+ * @param graph entity-filtering graph associated with entity class.
+ */
+ public EntityProcessorContextImpl(final Type type, final Method method, final EntityGraph graph) {
+ this(type, null, null, method, graph);
+ }
+
+ /**
+ * Create entity processor context for processing entity accessors.
+ *
+ * @param type type on entity processor context.
+ * @param clazz entity class.
+ * @param field entity property field.
+ * @param method entity property method.
+ * @param graph entity-filtering graph associated with entity class.
+ */
+ public EntityProcessorContextImpl(final Type type, final Class<?> clazz, final Field field, final Method method,
+ final EntityGraph graph) {
+ this.type = type;
+ this.clazz = clazz;
+ this.field = field;
+ this.method = method;
+ this.graph = graph;
+ }
+
+ @Override
+ public Type getType() {
+ return type;
+ }
+
+ @Override
+ public Class<?> getEntityClass() {
+ return clazz;
+ }
+
+ @Override
+ public Field getField() {
+ return field;
+ }
+
+ @Override
+ public Method getMethod() {
+ return method;
+ }
+
+ @Override
+ public EntityGraph getEntityGraph() {
+ return graph;
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ObjectGraphImpl.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ObjectGraphImpl.java
new file mode 100644
index 0000000..798a74f
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ObjectGraphImpl.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.glassfish.jersey.internal.util.collection.Views;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.ObjectGraph;
+import org.glassfish.jersey.message.filtering.spi.ScopeProvider;
+
+/**
+ * Default implementation of {@link ObjectGraph}.
+ *
+ * @author Michal Gajdos
+ */
+final class ObjectGraphImpl implements ObjectGraph {
+
+ private final Set<String> filteringScopes;
+
+ private final Map<Class<?>, EntityGraph> classToGraph;
+ private final EntityGraph graph;
+
+ private Set<String> fields;
+ private Map<String, ObjectGraph> subgraphs;
+
+ ObjectGraphImpl(final Map<Class<?>, EntityGraph> classToGraph, final EntityGraph graph, final Set<String> filteringScopes) {
+ this.filteringScopes = filteringScopes;
+
+ this.classToGraph = classToGraph;
+ this.graph = graph;
+ }
+
+ @Override
+ public Class<?> getEntityClass() {
+ return graph.getEntityClass();
+ }
+
+ @Override
+ public Set<String> getFields() {
+ return getFields(null);
+ }
+
+ @Override
+ public Set<String> getFields(final String parent) {
+ final Set<String> childFilteringScopes = getFilteringScopes(parent);
+ if (fields == null) {
+ fields = graph.getFields(
+ Views.setUnionView(
+ childFilteringScopes,
+ Collections.singleton(ScopeProvider.DEFAULT_SCOPE)));
+ }
+ return fields;
+ }
+
+ @Override
+ public Map<String, ObjectGraph> getSubgraphs() {
+ return getSubgraphs(null);
+ }
+
+ @Override
+ public Map<String, ObjectGraph> getSubgraphs(final String parent) {
+ final Set<String> childFilteringScopes = getFilteringScopes(parent);
+
+ if (subgraphs == null) {
+ final Map<String, Class<?>> contextSubgraphs = graph.getSubgraphs(childFilteringScopes);
+ contextSubgraphs.putAll(graph.getSubgraphs(ScopeProvider.DEFAULT_SCOPE));
+
+ subgraphs = Views.mapView(contextSubgraphs, new Function<Class<?>, ObjectGraph>() {
+
+ @Override
+ public ObjectGraph apply(final Class<?> clazz) {
+ final EntityGraph entityGraph = classToGraph.get(clazz);
+
+ return entityGraph == null
+ ? new EmptyObjectGraph(clazz)
+ : new ObjectGraphImpl(classToGraph, entityGraph, filteringScopes);
+ }
+ });
+ }
+ return subgraphs;
+ }
+
+ private Set<String> getFilteringScopes(final String parent) {
+ Set<String> childFilteringScopes = new HashSet<>();
+ if (filteringScopes.contains(SelectableScopeResolver.DEFAULT_SCOPE) || parent == null) {
+ childFilteringScopes = filteringScopes;
+ } else {
+ for (final String filteringScope : filteringScopes) {
+ final Pattern p = Pattern.compile(SelectableScopeResolver.PREFIX + parent + "\\.(\\w+)(\\.\\w+)*$");
+ final Matcher m = p.matcher(filteringScope);
+ if (m.matches()) {
+ childFilteringScopes.add(SelectableScopeResolver.PREFIX + m.group(1));
+ } else {
+ childFilteringScopes.add(filteringScope);
+ }
+ }
+ }
+ return childFilteringScopes;
+ }
+
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ObjectGraphProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ObjectGraphProvider.java
new file mode 100644
index 0000000..5f911e3
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ObjectGraphProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import org.glassfish.jersey.message.filtering.spi.AbstractObjectProvider;
+import org.glassfish.jersey.message.filtering.spi.ObjectGraph;
+
+/**
+ * {@link org.glassfish.jersey.message.filtering.spi.ObjectProvider Object provider} and
+ * {@link org.glassfish.jersey.message.filtering.spi.ObjectGraphTransformer object graph transformer} implementation for
+ * {@link ObjectGraph object graph}. These providers are present in runtime by default.
+ *
+ * @author Michal Gajdos
+ */
+final class ObjectGraphProvider extends AbstractObjectProvider<ObjectGraph> {
+
+ @Override
+ public ObjectGraph transform(final ObjectGraph graph) {
+ return graph;
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityAnnotations.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityAnnotations.java
new file mode 100644
index 0000000..55f55fe
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityAnnotations.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+
+import org.glassfish.jersey.internal.inject.AnnotationLiteral;
+
+/**
+ * Convenience utility methods for creating instances of security annotations.
+ *
+ * @author Michal Gajdos
+ */
+public final class SecurityAnnotations {
+
+ /**
+ * Create {@link RolesAllowed} annotation implementation for given set of roles.
+ *
+ * @param roles roles to be part of the annotation.
+ * @return annotation implementation.
+ */
+ public static RolesAllowed rolesAllowed(final String... roles) {
+ final List<String> list = new ArrayList<>(roles.length);
+ for (final String role : roles) {
+ if (role != null) {
+ list.add(role);
+ }
+ }
+ return new RolesAllowedImpl(list.toArray(new String[list.size()]));
+ }
+
+ /**
+ * Create {@link PermitAll} annotation implementation.
+ *
+ * @return annotation implementation.
+ */
+ public static PermitAll permitAll() {
+ return new PermitAllImpl();
+ }
+
+ /**
+ * Create {@link DenyAll} annotation implementation.
+ *
+ * @return annotation implementation.
+ */
+ public static DenyAll denyAll() {
+ return new DenyAllImpl();
+ }
+
+ /**
+ * DenyAll annotation implementation.
+ */
+ @SuppressWarnings("ClassExplicitlyAnnotation")
+ private static final class RolesAllowedImpl extends AnnotationLiteral<RolesAllowed> implements RolesAllowed {
+
+ private final String[] roles;
+
+ private RolesAllowedImpl(final String[] roles) {
+ this.roles = roles;
+ }
+
+ @Override
+ public String[] value() {
+ return roles;
+ }
+ }
+
+ /**
+ * DenyAll annotation implementation.
+ */
+ @SuppressWarnings("ClassExplicitlyAnnotation")
+ private static final class DenyAllImpl extends AnnotationLiteral<DenyAll> implements DenyAll {
+ }
+
+ /**
+ * PermitAll annotation implementation.
+ */
+ @SuppressWarnings("ClassExplicitlyAnnotation")
+ private static class PermitAllImpl extends AnnotationLiteral<PermitAll> implements PermitAll {
+ }
+
+ /**
+ * Prevent instantiation.
+ */
+ private SecurityAnnotations() {
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityEntityFilteringFeature.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityEntityFilteringFeature.java
new file mode 100644
index 0000000..729917c
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityEntityFilteringFeature.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering;
+
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
+
+/**
+ * {@link Feature} used to add support for Java Security annotations (<code>javax.annotation.security</code>) for Entity Data
+ * Filtering feature.
+ * <p>
+ * Supported annotations are:
+ * <ul>
+ * <li>{@link javax.annotation.security.PermitAll}</li>
+ * <li>{@link javax.annotation.security.RolesAllowed}</li>
+ * <li>{@link javax.annotation.security.DenyAll}</li>
+ * </ul>
+ * </p>
+ * <p>
+ * It is sufficient to annotate only property accessors of an entity without annotating resource method / resource class although
+ * it is not recommended.
+ * </p>
+ * Note: This feature also registers the {@link EntityFilteringFeature}.
+ *
+ * @author Michal Gajdos
+ * @see org.glassfish.jersey.message.filtering.EntityFilteringFeature
+ */
+public final class SecurityEntityFilteringFeature implements Feature {
+
+ @Override
+ public boolean configure(final FeatureContext context) {
+ final Configuration config = context.getConfiguration();
+
+ if (!config.isRegistered(SecurityEntityProcessor.class)) {
+ // RolesAllowed feature.
+ if (!config.isRegistered(RolesAllowedDynamicFeature.class)) {
+ context.register(RolesAllowedDynamicFeature.class);
+ }
+
+ // Binder (FilteringObjectProvider/FilteringGraphTransformer).
+ if (!config.isRegistered(EntityFilteringBinder.class)) {
+ context.register(new EntityFilteringBinder());
+ }
+
+ // Entity Processors.
+ context.register(SecurityEntityProcessor.class);
+ if (!config.isRegistered(DefaultEntityProcessor.class)) {
+ context.register(DefaultEntityProcessor.class);
+ }
+
+ // Scope Providers.
+ context.register(SecurityScopeResolver.class);
+ if (RuntimeType.SERVER.equals(config.getRuntimeType())) {
+ context.register(SecurityServerScopeResolver.class);
+ }
+
+ // Scope Resolver.
+ if (RuntimeType.SERVER == config.getRuntimeType()) {
+ context.register(SecurityServerScopeProvider.class);
+ } else {
+ context.register(CommonScopeProvider.class);
+ }
+
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityEntityProcessor.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityEntityProcessor.java
new file mode 100644
index 0000000..7971b67
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityEntityProcessor.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import javax.annotation.Priority;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.message.filtering.spi.AbstractEntityProcessor;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessor;
+
+/**
+ * {@link EntityProcessor Entity processor} handling security annotations on model entity classes.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+@Priority(Integer.MAX_VALUE - 3000)
+final class SecurityEntityProcessor extends AbstractEntityProcessor {
+
+ @Override
+ protected Result process(final String fieldName, final Class<?> fieldClass, final Annotation[] fieldAnnotations,
+ final Annotation[] annotations, final EntityGraph graph) {
+ if (annotations.length > 0) {
+ final Set<String> filteringScopes = SecurityHelper.getFilteringScopes(annotations);
+
+ if (filteringScopes == null) {
+ return EntityProcessor.Result.ROLLBACK;
+ } else if (!filteringScopes.isEmpty()) {
+ if (fieldName != null) {
+ // For field.
+ addFilteringScopes(fieldName, fieldClass, filteringScopes, graph);
+ } else {
+ // For entire class into graph.
+ addGlobalScopes(filteringScopes, graph);
+ }
+ }
+ }
+
+ return EntityProcessor.Result.APPLY;
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityHelper.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityHelper.java
new file mode 100644
index 0000000..69fc968
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityHelper.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.core.SecurityContext;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+
+/**
+ * Utility methods for security Entity Data Filtering.
+ *
+ * @author Michal Gajdos
+ */
+final class SecurityHelper {
+
+ private static final Set<String> roles = new HashSet<>();
+
+ /**
+ * Get entity-filtering scopes of security annotations present among given annotations.
+ * <p>
+ * A scope look like:
+ * <ul>
+ * <li><fully qualified annotation class name>, or</li>
+ * <li><fully qualified annotation class name>_<role></li>
+ * </ul>
+ * </p>
+ *
+ * @param annotations a list of annotations (doesn't need to contain only security annotations)
+ * @return a set of entity-filtering scopes.
+ */
+ static Set<String> getFilteringScopes(final Annotation[] annotations) {
+ return getFilteringScopes(null, annotations);
+ }
+
+ /**
+ * Get entity-filtering scopes of security annotations present among given annotations with respect to given
+ * {@link SecurityContext}. Resulting set contains only scopes that pass the security context check.
+ * <p>
+ * A scope look like:
+ * <ul>
+ * <li><fully qualified annotation class name>, or</li>
+ * <li><fully qualified annotation class name>_<role></li>
+ * </ul>
+ * </p>
+ *
+ * @param securityContext security context to check whether a user is in specified logical role.
+ * @param annotations a list of annotations (doesn't need to contain only security annotations)
+ * @return a set of entity-filtering scopes.
+ */
+ static Set<String> getFilteringScopes(final SecurityContext securityContext, final Annotation[] annotations) {
+ if (annotations.length == 0) {
+ return Collections.emptySet();
+ }
+
+ for (final Annotation annotation : annotations) {
+ if (annotation instanceof RolesAllowed) {
+ final Set<String> bindings = new HashSet<>();
+
+ for (final String role : ((RolesAllowed) annotation).value()) {
+ if (securityContext == null || securityContext.isUserInRole(role)) {
+ bindings.add(getRolesAllowedScope(role));
+ }
+ }
+
+ return bindings;
+ } else if (annotation instanceof PermitAll) {
+ return FilteringHelper.getDefaultFilteringScope();
+ } else if (annotation instanceof DenyAll) {
+ return null;
+ }
+ }
+
+ return Collections.emptySet();
+ }
+
+ /**
+ * Get entity-filtering scope for {@link RolesAllowed}s role.
+ *
+ * @param role role to retrieve entity-filtering scope for.
+ * @return entity-filtering scope.
+ */
+ static String getRolesAllowedScope(final String role) {
+ roles.add(role);
+ return RolesAllowed.class.getName() + "_" + role;
+ }
+
+ /**
+ * Get authorization roles that has been derived from examining entity classes.
+ *
+ * @return already processed authorization roles.
+ */
+ static Set<String> getProcessedRoles() {
+ return roles;
+ }
+
+ /**
+ * Prevent instantiation.
+ */
+ private SecurityHelper() {
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityScopeResolver.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityScopeResolver.java
new file mode 100644
index 0000000..896d2e3
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityScopeResolver.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.message.filtering.spi.ScopeResolver;
+
+/**
+ * {@link ScopeResolver scope provider} resolving entity-filtering scopes from security annotations.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+final class SecurityScopeResolver implements ScopeResolver {
+
+ @Override
+ public Set<String> resolve(final Annotation[] annotations) {
+ return SecurityHelper.getFilteringScopes(annotations);
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityServerScopeProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityServerScopeProvider.java
new file mode 100644
index 0000000..6be0444
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityServerScopeProvider.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.SecurityContext;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * @author Michal Gajdos
+ */
+@Singleton
+@ConstrainedTo(RuntimeType.SERVER)
+final class SecurityServerScopeProvider extends ServerScopeProvider {
+
+ @Context
+ private SecurityContext securityContext;
+
+ @Inject
+ public SecurityServerScopeProvider(final Configuration config, final InjectionManager injectionManager) {
+ super(config, injectionManager);
+ }
+
+ @Override
+ public Set<String> getFilteringScopes(final Annotation[] entityAnnotations, final boolean defaultIfNotFound) {
+ Set<String> filteringScope = super.getFilteringScopes(entityAnnotations, false);
+
+ if (filteringScope.isEmpty()) {
+ filteringScope = new HashSet<>();
+
+ // Get all roles collected from entities and check with current security context.
+ for (final String role : SecurityHelper.getProcessedRoles()) {
+ if (securityContext.isUserInRole(role)) {
+ filteringScope.add(SecurityHelper.getRolesAllowedScope(role));
+ }
+ }
+ }
+
+ // Use default scope if not in other scope.
+ return returnFilteringScopes(filteringScope, defaultIfNotFound);
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityServerScopeResolver.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityServerScopeResolver.java
new file mode 100644
index 0000000..0504de5
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityServerScopeResolver.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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.SecurityContext;
+
+import javax.annotation.Priority;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.message.filtering.spi.ScopeResolver;
+
+/**
+ * Server-side {@link ScopeResolver scope provider} resolving entity-filtering scopes from security annotations
+ * with respect to user's roles defined in {@link SecurityContext}.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+@Priority(Priorities.ENTITY_CODER + 100)
+@ConstrainedTo(RuntimeType.SERVER)
+final class SecurityServerScopeResolver implements ScopeResolver {
+
+ @Context
+ private SecurityContext securityContext;
+
+ @Override
+ public Set<String> resolve(final Annotation[] annotations) {
+ return SecurityHelper.getFilteringScopes(securityContext, annotations);
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableEntityFilteringFeature.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableEntityFilteringFeature.java
new file mode 100644
index 0000000..e89bea1
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableEntityFilteringFeature.java
@@ -0,0 +1,54 @@
+/*
+ * 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.message.filtering;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+/**
+ * {@link Feature} used to add support for custom query parameter filtering for
+ * Entity Data Filtering feature. </p> Note: This feature also registers the
+ * {@link EntityFilteringFeature}.
+ *
+ * @author Andy Pemberton (pembertona at gmail.com)
+ * @see org.glassfish.jersey.message.filtering.EntityFilteringFeature
+ */
+public final class SelectableEntityFilteringFeature implements Feature {
+
+ public static final String QUERY_PARAM_NAME = "jersey.config.entityFiltering.selectable.query";
+
+ @Override
+ public boolean configure(final FeatureContext context) {
+ final Configuration config = context.getConfiguration();
+
+ if (!config.isRegistered(SelectableEntityProcessor.class)) {
+
+ // register EntityFilteringFeature
+ if (!config.isRegistered(EntityFilteringFeature.class)) {
+ context.register(EntityFilteringFeature.class);
+ }
+ // Entity Processors.
+ context.register(SelectableEntityProcessor.class);
+ // Scope Resolver.
+ context.register(SelectableScopeResolver.class);
+
+ return true;
+ }
+ return true;
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableEntityProcessor.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableEntityProcessor.java
new file mode 100644
index 0000000..0f478a6
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableEntityProcessor.java
@@ -0,0 +1,52 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Priority;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.message.filtering.spi.AbstractEntityProcessor;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessor;
+
+@Singleton
+@Priority(Integer.MAX_VALUE - 5000)
+public class SelectableEntityProcessor extends AbstractEntityProcessor {
+
+ protected Result process(final String fieldName, final Class<?> fieldClass, final Annotation[] fieldAnnotations,
+ final Annotation[] annotations, final EntityGraph graph) {
+
+ if (fieldName != null) {
+ final Set<String> scopes = new HashSet<>();
+
+ // add default selectable scope in case of none requested
+ scopes.add(SelectableScopeResolver.DEFAULT_SCOPE);
+
+ // add specific scope in case of specific request
+ scopes.add(SelectableScopeResolver.PREFIX + fieldName);
+
+ addFilteringScopes(fieldName, fieldClass, scopes, graph);
+ }
+
+ return EntityProcessor.Result.APPLY;
+ }
+
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableScopeResolver.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableScopeResolver.java
new file mode 100644
index 0000000..6e22afb
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableScopeResolver.java
@@ -0,0 +1,95 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.util.Tokenizer;
+import org.glassfish.jersey.message.filtering.spi.ScopeResolver;
+
+@Singleton
+public class SelectableScopeResolver implements ScopeResolver {
+
+ /**
+ * Prefix for all selectable scopes
+ */
+ public static final String PREFIX = SelectableScopeResolver.class.getName() + "_";
+
+ /**
+ * Scope used for selecting all fields, i.e.: when no filter is applied
+ */
+ public static final String DEFAULT_SCOPE = PREFIX + "*";
+
+ /**
+ * Query parameter name for selectable feature, set to default value
+ */
+ private static String SELECTABLE_PARAM_NAME = "select";
+
+ @Context
+ private Configuration configuration;
+
+ @Context
+ private UriInfo uriInfo;
+
+ @PostConstruct
+ private void init() {
+ final String paramName = (String) configuration.getProperty(SelectableEntityFilteringFeature.QUERY_PARAM_NAME);
+ SELECTABLE_PARAM_NAME = paramName != null ? paramName : SELECTABLE_PARAM_NAME;
+ }
+
+ @Override
+ public Set<String> resolve(final Annotation[] annotations) {
+ final Set<String> scopes = new HashSet<>();
+
+ final List<String> fields = uriInfo.getQueryParameters().get(SELECTABLE_PARAM_NAME);
+ if (fields != null && !fields.isEmpty()) {
+ for (final String field : fields) {
+ scopes.addAll(getScopesForField(field));
+ }
+ } else {
+ scopes.add(DEFAULT_SCOPE);
+ }
+ return scopes;
+ }
+
+ private Set<String> getScopesForField(final String fieldName) {
+ final Set<String> scopes = new HashSet<>();
+
+ // add specific scope in case of specific request
+ final String[] fields = Tokenizer.tokenize(fieldName, ",");
+ for (final String field : fields) {
+ final String[] subfields = Tokenizer.tokenize(field, ".");
+ // in case of nested path, add first level as stand-alone to ensure subgraph is added
+ scopes.add(SelectableScopeResolver.PREFIX + subfields[0]);
+ if (subfields.length > 1) {
+ scopes.add(SelectableScopeResolver.PREFIX + field);
+ }
+ }
+
+ return scopes;
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ServerScopeProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ServerScopeProvider.java
new file mode 100644
index 0000000..872317b
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ServerScopeProvider.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+
+import javax.annotation.Priority;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.util.collection.DataStructures;
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.model.ResourceMethod;
+
+/**
+ * Server-side implementation of {@link org.glassfish.jersey.message.filtering.spi.ScopeProvider scope provider}. In addition to
+ * {@link CommonScopeProvider base implementation} this class provides entity-filtering scopes by examining matched resource
+ * method and sub-resource locators. This examination comes into play only in case if entity-filtering scopes cannot be found in
+ * entity annotations or application configuration.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+@Priority(Priorities.ENTITY_CODER + 200)
+@ConstrainedTo(RuntimeType.SERVER)
+class ServerScopeProvider extends CommonScopeProvider {
+
+ @Inject
+ private Provider<ExtendedUriInfo> uriInfoProvider;
+
+ private final ConcurrentMap<String, Set<String>> uriToContexts;
+
+ /**
+ * Create new server scope provider with injected {@link Configuration configuration} and
+ * {@link InjectionManager jersey injection manager}.
+ */
+ @Inject
+ public ServerScopeProvider(final Configuration config, final InjectionManager injectionManager) {
+ super(config, injectionManager);
+ this.uriToContexts = DataStructures.createConcurrentMap();
+ }
+
+ @Override
+ public Set<String> getFilteringScopes(final Annotation[] entityAnnotations, final boolean defaultIfNotFound) {
+ Set<String> filteringScope = super.getFilteringScopes(entityAnnotations, false);
+
+ if (filteringScope.isEmpty()) {
+ final ExtendedUriInfo uriInfo = uriInfoProvider.get();
+ final String path = uriInfo.getPath();
+
+ if (uriToContexts.containsKey(path)) {
+ return uriToContexts.get(path);
+ }
+
+ for (final ResourceMethod method : ServerScopeProvider.getMatchedMethods(uriInfo)) {
+ final Invocable invocable = method.getInvocable();
+
+ mergeFilteringScopes(filteringScope,
+ getFilteringScopes(invocable.getHandlingMethod(), invocable.getHandler().getHandlerClass()));
+
+ if (!filteringScope.isEmpty()) {
+ uriToContexts.putIfAbsent(path, filteringScope);
+ return filteringScope;
+ }
+ }
+ }
+
+ // Use default scope if not in other scope.
+ return returnFilteringScopes(filteringScope, defaultIfNotFound);
+ }
+
+ /**
+ * Get entity-filtering scopes from examining annotations present on resource method and resource class.
+ *
+ * @param resourceMethod matched resource method to be examined.
+ * @param resourceClass matched resource class to be examined.
+ * @return entity-filtering scopes or an empty set if the scopes cannot be obtained.
+ */
+ protected Set<String> getFilteringScopes(final Method resourceMethod, final Class<?> resourceClass) {
+ // Method annotations first.
+ Set<String> scope = getFilteringScopes(resourceMethod.getAnnotations());
+
+ // Class annotations second.
+ if (scope.isEmpty()) {
+ scope = getFilteringScopes(resourceClass.getAnnotations());
+ }
+
+ return scope;
+ }
+
+ private static List<ResourceMethod> getMatchedMethods(final ExtendedUriInfo uriInfo) {
+ final List<ResourceMethod> matchedResourceLocators = uriInfo.getMatchedResourceLocators();
+ final List<ResourceMethod> methods = new ArrayList<>(1 + matchedResourceLocators.size());
+
+ methods.add(uriInfo.getMatchedResourceMethod());
+ methods.addAll(matchedResourceLocators);
+
+ return methods;
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/package-info.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/package-info.java
new file mode 100644
index 0000000..7741b43
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/package-info.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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
+ */
+
+/**
+ * Support for Entity Data Filtering in Jersey.
+ * <p/>
+ * To use Entity Data Filtering one of the provided {@link javax.ws.rs.core.Feature features} has to be registered in an
+ * application:
+ * <ul>
+ * <li>{@link org.glassfish.jersey.message.filtering.EntityFilteringFeature} - adds support for entity-filtering
+ * annotations based on {@link org.glassfish.jersey.message.filtering.EntityFiltering} meta-annotation.</li>
+ * <li>{@link org.glassfish.jersey.message.filtering.SecurityEntityFilteringFeature} - add support for entity-filtering using
+ * Java Security annotations (<code>javax.annotation.security</code>).</li>
+ * </ul>
+ * <p/>
+ * To define own entity-filtering annotations, refer to the {@link org.glassfish.jersey.message.filtering.EntityFiltering}
+ * meta-annotation.
+ *
+ * @since 2.3
+ */
+package org.glassfish.jersey.message.filtering;
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/AbstractEntityProcessor.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/AbstractEntityProcessor.java
new file mode 100644
index 0000000..ad86114
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/AbstractEntityProcessor.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering.spi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Set;
+
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Common {@link EntityProcessor entity processor} supposed to be used as a base class for custom implementations. Provides
+ * convenient methods for adding entity-filtering scopes to {@link EntityGraph entity graph} as well as a common implementation
+ * of {@link #process(org.glassfish.jersey.message.filtering.spi.EntityProcessorContext)}.
+ *
+ * @author Michal Gajdos
+ */
+public abstract class AbstractEntityProcessor implements EntityProcessor {
+
+ @Override
+ public EntityProcessor.Result process(final EntityProcessorContext context) {
+ switch (context.getType()) {
+ case CLASS_READER:
+ case CLASS_WRITER:
+ return process(null, null, FilteringHelper.EMPTY_ANNOTATIONS, context.getEntityClass().getDeclaredAnnotations(),
+ context.getEntityGraph());
+
+ case PROPERTY_READER:
+ case PROPERTY_WRITER:
+ case METHOD_READER:
+ case METHOD_WRITER:
+ final Field field = context.getField();
+ final Method method = context.getMethod();
+
+ final boolean isProperty = field != null;
+ String fieldName;
+ Type fieldType;
+
+ if (isProperty) {
+ fieldName = field.getName();
+ fieldType = field.getGenericType();
+ } else {
+ fieldName = ReflectionHelper.getPropertyName(method);
+ fieldType = ReflectionHelper.isGetter(method) ? method.getGenericReturnType() : method
+ .getGenericParameterTypes()[0];
+ }
+
+ return process(fieldName, FilteringHelper.getEntityClass(fieldType), getAnnotations(field),
+ getAnnotations(method), context.getEntityGraph());
+
+ default:
+ // NOOP.
+ }
+ return EntityProcessor.Result.SKIP;
+ }
+
+ private Annotation[] getAnnotations(final AccessibleObject accessibleObject) {
+ return accessibleObject == null ? FilteringHelper.EMPTY_ANNOTATIONS : accessibleObject.getDeclaredAnnotations();
+ }
+
+ /**
+ * Method is called from the default implementation of
+ * {@link #process(org.glassfish.jersey.message.filtering.spi.EntityProcessorContext)} and is supposed to be overridden by
+ * custom implementations of this class.
+ *
+ * @param fieldName name of the field (can be {@code null}).
+ * @param fieldClass class of the field (can be {@code null}).
+ * @param fieldAnnotations annotations associated with the field (cannot be {@code null}).
+ * @param annotations annotations associated with class/accessor (cannot be {@code null}).
+ * @param graph entity graph to be processed.
+ * @return result of the processing (default is {@link Result#SKIP}).
+ */
+ protected Result process(final String fieldName, final Class<?> fieldClass, final Annotation[] fieldAnnotations,
+ final Annotation[] annotations, final EntityGraph graph) {
+ return Result.SKIP;
+ }
+
+ /**
+ * Add entity-filtering scopes of a field to an entity-graph. The method determines whether the field should be added as a
+ * simple field or a subgraph.
+ *
+ * @param field name of a field to be added to the graph.
+ * @param fieldClass class of the field.
+ * @param filteringScopes entity-filtering scopes the field will be associated with in the graph.
+ * @param graph entity graph the field will be added to.
+ */
+ protected final void addFilteringScopes(final String field, final Class<?> fieldClass, final Set<String> filteringScopes,
+ final EntityGraph graph) {
+ if (!filteringScopes.isEmpty()) {
+ if (FilteringHelper.filterableEntityClass(fieldClass)) {
+ graph.addSubgraph(field, fieldClass, filteringScopes);
+ } else {
+ graph.addField(field, filteringScopes);
+ }
+ }
+ }
+
+ /**
+ * Add entity-filtering scopes into given graph. This method should be called only in class-level context.
+ *
+ * @param filteringScopes entity-filtering scopes to be added to graph.
+ * @param graph entity graph to be enhanced by new scopes.
+ */
+ protected final void addGlobalScopes(final Set<String> filteringScopes, final EntityGraph graph) {
+ graph.addFilteringScopes(filteringScopes);
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/AbstractObjectProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/AbstractObjectProvider.java
new file mode 100644
index 0000000..94118cd
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/AbstractObjectProvider.java
@@ -0,0 +1,184 @@
+/*
+ * 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.message.filtering.spi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.glassfish.jersey.internal.guava.Cache;
+import org.glassfish.jersey.internal.guava.CacheBuilder;
+
+/**
+ * Common implementation of {@link ObjectProvider object provider} and {@link ObjectGraphTransformer object graph transformer}.
+ * <p>
+ * Extensions of this class must provide a type of entity-filtering object (via generic type parameter) the requesting provider
+ * (e.g. message body worker) is familiar with and an implementation of
+ * {@link ObjectGraphTransformer#transform(ObjectGraph)} method for this type.
+ * </p>
+ *
+ * @param <T> representation of entity data filtering requested by provider.
+ * @author Michal Gajdos
+ */
+public abstract class AbstractObjectProvider<T> implements ObjectProvider<T>, ObjectGraphTransformer<T> {
+
+ private static final int PROVIDER_CACHE_SIZE = 1000;
+
+ private final Cache<EntityContext, T> filteringObjects = CacheBuilder.newBuilder().maximumSize(PROVIDER_CACHE_SIZE).build();
+
+ @Inject
+ private ScopeProvider scopeProvider;
+
+ @Inject
+ private EntityInspector entityInspector;
+
+ @Inject
+ private EntityGraphProvider graphProvider;
+
+ @Override
+ public final T getFilteringObject(final Type genericType, final boolean forWriter, final Annotation... annotations) {
+ return getFilteringObject(FilteringHelper.getEntityClass(genericType), forWriter, annotations);
+ }
+
+ private T getFilteringObject(final Class<?> entityClass, final boolean forWriter, final Annotation... annotations) {
+ if (FilteringHelper.filterableEntityClass(entityClass)) {
+ // Inspect.
+ entityInspector.inspect(entityClass, forWriter);
+
+ // Obtain runtime/resource scope.
+ final Set<String> filteringScope = scopeProvider.getFilteringScopes(getEntityAnnotations(annotations), true);
+
+ // Look into the cache.
+ final EntityContext entityContext = new EntityContext(entityClass, filteringScope);
+ T filteringObject = filteringObjects.getIfPresent(entityContext);
+
+ // Create new if not available.
+ if (filteringObject == null) {
+ filteringObject = createFilteringObject(entityClass, filteringScope, forWriter);
+ filteringObjects.put(entityContext, filteringObject);
+ }
+
+ return filteringObject;
+ }
+ return null;
+ }
+
+ /**
+ * Get entity annotations passed to request/response. This method filters annotations that are proxy instances (proxy
+ * annotations are taken from resource method and passed in this list).
+ *
+ * @param annotations annotations obtained from provider.
+ * @return annoations passed to request/response.
+ */
+ private Annotation[] getEntityAnnotations(final Annotation[] annotations) {
+ final ArrayList<Annotation> entityAnnotations = new ArrayList<>();
+
+ for (final Annotation annotation : annotations) {
+ if (!(annotation instanceof Proxy)) {
+ entityAnnotations.add(annotation);
+ }
+ }
+
+ return entityAnnotations.toArray(new Annotation[entityAnnotations.size()]);
+ }
+
+ /**
+ * Create entity-filtering object after this object has not been found in the cache.
+ *
+ * @param entityClass entity class the entity-filtering object should be created for.
+ * @param filteringScopes entity-filtering scopes to create the entity-filtering object for.
+ * @param forWriter flag determining whether the class should be examined for reader or writer.
+ * @return entity-filtering object.
+ */
+ private T createFilteringObject(final Class<?> entityClass, final Set<String> filteringScopes, final boolean forWriter) {
+ // Obtain the filtering object.
+ return transform(graphProvider.createObjectGraph(entityClass, filteringScopes, forWriter));
+ }
+
+ /**
+ * A helper method for a creation of an immutable set based on a provided set together with a given item.
+ *
+ * @param set The set to create the immutable set from.
+ * @param item The item to add to the set before it's made immutable.
+ * @return The immutable set from given set and item.
+ */
+ protected Set<String> immutableSetOf(final Set<String> set, final String item) {
+ final Set<String> duplicate = new HashSet<>(set);
+ duplicate.add(item);
+ return Collections.unmodifiableSet(duplicate);
+ }
+
+ /**
+ * Creates a string identifier of a sub-graph.
+ *
+ * @param parent The parent class.
+ * @param field The field name.
+ * @param fieldClass The class of the field.
+ * @return The string identifier of the sub-graph.
+ */
+ protected String subgraphIdentifier(final Class<?> parent, final String field, final Class<?> fieldClass) {
+ return parent.getName() + "_" + field + "_" + fieldClass.getName();
+ }
+
+ /**
+ * Class to be used as a key in cache ({@code EntityContext} -> filtering object) when processing similar requests.
+ */
+ private static final class EntityContext {
+
+ private final Class<?> entityClass;
+
+ private final Set<String> filteringContext;
+
+ /**
+ * Create entity context class for given entity class and set of entity-filtering scopes.
+ *
+ * @param entityClass entity class.
+ * @param filteringScopes entity-filtering scopes.
+ */
+ private EntityContext(final Class<?> entityClass, final Set<String> filteringScopes) {
+ this.entityClass = entityClass;
+ this.filteringContext = filteringScopes;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof EntityContext)) {
+ return false;
+ }
+
+ final EntityContext that = (EntityContext) o;
+
+ return entityClass.equals(that.entityClass) && filteringContext.equals(that.filteringContext);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = entityClass.hashCode();
+ result = 47 * result + filteringContext.hashCode();
+ return result;
+ }
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityGraph.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityGraph.java
new file mode 100644
index 0000000..6dc95e1
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityGraph.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering.spi;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Class available to {@link EntityProcessor entity-filtering processors} providing means to add/remove entity-filtering scopes
+ * (e.g. based on annotations) for entity members.
+ * <p/>
+ * Differences between this class and {@link ObjectGraph object graph}:
+ * <ul>
+ * <li>{@code EntityGraph} can be modified, {@code ObjectGraph} is read-only.</li>
+ * <li>{@code EntityGraph} contains information about all entity-filtering scopes found during inspecting an entity class,
+ * {@code ObjectGraph} provides information about entity to create a filtering object for a subset of these scopes
+ * (which are determined from the current context).</li>
+ * </ul>
+ * <p/>
+ * Note: Definition of entity-filtering scopes can be found in {@link ScopeResolver}.
+ *
+ * @author Michal Gajdos
+ * @see ScopeResolver
+ */
+public interface EntityGraph {
+
+ /**
+ * Add a field into this graph for all existing entity-filtering scopes.
+ *
+ * @param fieldName name of the field to be added.
+ * @return an entity-filtering graph instance.
+ */
+ public EntityGraph addField(final String fieldName);
+
+ /**
+ * Add a field into this graph for given list of entity-filtering scopes.
+ *
+ * @param fieldName name of the field to be added.
+ * @param filteringScopes entity-filtering scopes for the field.
+ * @return an entity-filtering graph instance.
+ */
+ public EntityGraph addField(final String fieldName, final String... filteringScopes);
+
+ /**
+ * Add a field into this graph for given set of entity-filtering scopes.
+ *
+ * @param fieldName name of the field to be added.
+ * @param filteringScopes entity-filtering scopes for the field.
+ * @return an entity-filtering graph instance.
+ */
+ public EntityGraph addField(final String fieldName, final Set<String> filteringScopes);
+
+ /**
+ * Add a subgraph into this graph for all existing entity-filtering scopes.
+ *
+ * @param fieldName name of the subgraph field to be added.
+ * @param fieldClass entity class representing the subgraph.
+ * @return an entity-filtering graph instance.
+ */
+ public EntityGraph addSubgraph(final String fieldName, final Class<?> fieldClass);
+
+ /**
+ * Add a subgraph into this graph for given list of entity-filtering scopes.
+ *
+ * @param fieldName name of the subgraph field to be added.
+ * @param fieldClass entity class representing the subgraph.
+ * @param filteringScopes entity-filtering scopes for the subgraph.
+ * @return an entity-filtering graph instance.
+ */
+ public EntityGraph addSubgraph(final String fieldName, final Class<?> fieldClass, final String... filteringScopes);
+
+ /**
+ * Add a subgraph into this graph for given set of entity-filtering scopes.
+ *
+ * @param fieldName name of the subgraph field to be added.
+ * @param fieldClass entity class representing the subgraph.
+ * @param filteringScopes entity-filtering scopes for the subgraph.
+ * @return an entity-filtering graph instance.
+ */
+ public EntityGraph addSubgraph(final String fieldName, final Class<?> fieldClass, final Set<String> filteringScopes);
+
+ /**
+ * Add a set of entity-filtering scopes to this graph.
+ *
+ * @param filteringScopes entity-filtering scopes to be added.
+ * @return an entity-filtering graph instance.
+ */
+ public EntityGraph addFilteringScopes(final Set<String> filteringScopes);
+
+ /**
+ * Determines whether a field/subgraph is present in ANY of the given scopes. If no scopes are given the return value
+ * determines whether the field is present in any scope.
+ *
+ * @param field field to be checked.
+ * @param filteringScope entity-filtering scope.
+ * @return {@code true} if field is present in the given scope, {@code false} otherwise.
+ */
+ public boolean presentInScope(final String field, final String filteringScope);
+
+ /**
+ * Determines whether a field/subgraph is present in ANY of the existing scopes.
+ *
+ * @param field field to be checked.
+ * @return {@code true} if field is present in ANY of the existing scopes, {@code false} otherwise.
+ */
+ public boolean presentInScopes(final String field);
+
+ /**
+ * Get an entity class this graph is created for.
+ *
+ * @return an entity class.
+ */
+ public Class<?> getEntityClass();
+
+ /**
+ * Get fields for given entity-filtering scope.
+ *
+ * @param filteringScope scope the returned fields have to be in.
+ * @return set of fields present in given scope.
+ */
+ public Set<String> getFields(final String filteringScope);
+
+ /**
+ * Get fields for given entity-filtering scopes.
+ *
+ * @param filteringScopes scopes the returned fields have to be in.
+ * @return set of fields present in given scopes.
+ */
+ public Set<String> getFields(final String... filteringScopes);
+
+ /**
+ * Get fields for given entity-filtering scopes.
+ *
+ * @param filteringScopes scopes the returned fields have to be in.
+ * @return set of fields present in given scopes.
+ */
+ public Set<String> getFields(final Set<String> filteringScopes);
+
+ /**
+ * Get all available entity-filtering scopes.
+ *
+ * @return all available entity-filtering scopes.
+ */
+ public Set<String> getFilteringScopes();
+
+ /**
+ * Get all available entity-filtering scopes defined on a class.
+ *
+ * @return all available entity-filtering scopes.
+ */
+ public Set<String> getClassFilteringScopes();
+
+ /**
+ * Get subgraphs for given entity-filtering scope.
+ *
+ * @param filteringScope scope the returned subgraphs have to be in.
+ * @return map of subgraphs present in given scope.
+ */
+ public Map<String, Class<?>> getSubgraphs(final String filteringScope);
+
+ /**
+ * Get subgraphs for given entity-filtering scopes.
+ *
+ * @param filteringScopes scopes the returned subgraphs have to be in.
+ * @return map of subgraphs present in given scopes.
+ */
+ public Map<String, Class<?>> getSubgraphs(final String... filteringScopes);
+
+ /**
+ * Get subgraphs for given entity-filtering scopes.
+ *
+ * @param filteringScopes scopes the returned subgraphs have to be in.
+ * @return map of subgraphs present in given scopes.
+ */
+ public Map<String, Class<?>> getSubgraphs(final Set<String> filteringScopes);
+
+ /**
+ * Remove a field/subgraph from the graph (all entity-filtering scopes).
+ *
+ * @param name name of the field/subgraph to be removed.
+ * @return an entity-filtering graph instance.
+ */
+ public EntityGraph remove(final String name);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityGraphProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityGraphProvider.java
new file mode 100644
index 0000000..1f67159
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityGraphProvider.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering.spi;
+
+import java.util.Set;
+
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Provides {@link EntityGraph entity graph} and {@link ObjectGraph object graph} instances.
+ *
+ * @author Michal Gajdos
+ */
+@Contract
+public interface EntityGraphProvider {
+
+ /**
+ * Get an entity graph for given class. New graph is created if no graph exists for given class.
+ *
+ * @param entityClass entity class the graph should be created for.
+ * @param forWriter flag determining whether the graph should be created for writer/reader.
+ * @return an entity graph.
+ */
+ public EntityGraph getOrCreateEntityGraph(final Class<?> entityClass, final boolean forWriter);
+
+ /**
+ * Get an empty entity graph for given class. New graph is created if the stored one is not an empty entity graph or no graph
+ * exists for given class. This method overrides the graph created by {@link #getOrCreateEntityGraph(Class, boolean)} method.
+ *
+ * @param entityClass entity class the graph should be created for.
+ * @param forWriter flag determining whether the graph should be created for writer/reader.
+ * @return an empty entity graph.
+ */
+ public EntityGraph getOrCreateEmptyEntityGraph(final Class<?> entityClass, final boolean forWriter);
+
+ /**
+ * Determine whether an entity graph for given entity class has been created by this provider.
+ *
+ * @param entityClass entity class for which the graph should be checked.
+ * @param forWriter flag determining whether the check should be in writer/reader graphs.
+ * @return {@code true} if the entity graph already exists, {@code false} otherwise.
+ */
+ public boolean containsEntityGraph(final Class<?> entityClass, final boolean forWriter);
+
+ /**
+ * Create an {@code ObjectGraph} for given parameters. Every time this method is called a new instance of object graph is
+ * created.
+ *
+ * @param entityClass entity class which the object graph should be created for.
+ * @param filteringScopes entity-filtering scopes the graph should be created for.
+ * @param forWriter flag determining whether the graph should be created for writer/reader.
+ * @return an entity-filtering object graph instance.
+ */
+ public ObjectGraph createObjectGraph(final Class<?> entityClass, final Set<String> filteringScopes, final boolean forWriter);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityInspector.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityInspector.java
new file mode 100644
index 0000000..d2ca722
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityInspector.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering.spi;
+
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Responsible for inspecting entity classes. This class invokes all available {@link EntityProcessor entity processors} with
+ * different {@link EntityProcessorContext contexts}.
+ *
+ * @author Michal Gajdos
+ */
+@Contract
+public interface EntityInspector {
+
+ /**
+ * Inspect entity class and create/update {@link EntityGraph} for reader/writer. The entity graph will be used to create
+ * entity-filtering object which is requested by {@code #createFilteringObject(...)}.
+ * <p>
+ * Method recursively inspects entity fields classes suitable for inspecting.
+ * </p>
+ * <p>
+ * Method uses {@link EntityProcessor}s for inspecting.
+ * </p>
+ *
+ * @param entityClass entity class to be examined.
+ * @param forWriter flag determining whether the class should be examined for reader or writer.
+ */
+ public void inspect(final Class<?> entityClass, final boolean forWriter);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityProcessor.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityProcessor.java
new file mode 100644
index 0000000..c7a9461
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityProcessor.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering.spi;
+
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Contract supposed to process entity classes for Entity Data Filtering. Implementations will be given a
+ * {@link EntityProcessorContext context} providing necessary information to process particular
+ * {@link EntityProcessorContext.Type context type}. Contexts are created for: class / properties / accessors.
+ *
+ * @author Michal Gajdos
+ */
+@Contract
+public interface EntityProcessor {
+
+ /**
+ * Result type of processing an context.
+ */
+ public enum Result {
+
+ /**
+ * Processing of an context resulted in modification of the provided entity graph.
+ */
+ APPLY,
+
+ /**
+ * Entity processor didn't modify the provided entity graph.
+ */
+ SKIP,
+
+ /**
+ * Rollback every entity graph modification done in current context.
+ */
+ ROLLBACK
+ }
+
+ /**
+ * Process given (class/property/accessor) {@link EntityProcessorContext context} by modifying provided {@link EntityGraph}.
+ *
+ * @param context context to be processed.
+ * @return result of processing a context.
+ */
+ public Result process(final EntityProcessorContext context);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityProcessorContext.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityProcessorContext.java
new file mode 100644
index 0000000..47efbd2
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityProcessorContext.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering.spi;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * {@link EntityProcessor Entity processor} context providing details about entity processing.
+ * <p/>
+ * The context contains the {@link Type} which distinguishes between types of context. There are various properties in the
+ * context (accessible by getters) and some of them might be relevant only to specific context types.
+ *
+ * @author Michal Gajdos
+ */
+public interface EntityProcessorContext {
+
+ /**
+ * The type of the context which describes in which entity processing phase the
+ * {@link EntityProcessor#process(EntityProcessorContext)} is triggered.
+ */
+ public enum Type {
+
+ /**
+ * Context created to process entity class when reading entity from an input stream into an Java object. Properties
+ * available for this type are: {@link #getEntityClass()}, {@link #getEntityGraph()}.
+ */
+ CLASS_READER,
+
+ /**
+ * Context created to process entity class when writing entity to an output stream from an Java object. Properties
+ * available for this type are: {@link #getEntityClass()}, {@link #getEntityGraph()}.
+ */
+ CLASS_WRITER,
+
+ /**
+ * Context created to process entity properties when reading entity from an input stream into an Java object. Properties
+ * available for this type are: {@link #getField()}, {@link #getMethod()}, {@link #getEntityGraph()}.
+ */
+ PROPERTY_READER,
+
+ /**
+ * Context created to process entity properties when writing entity to an output stream from an Java object. Properties
+ * available for this type are: {@link #getField()}, {@link #getMethod()}, {@link #getEntityGraph()}.
+ */
+ PROPERTY_WRITER,
+
+ /**
+ * Context created to process property accessors when reading entity from an input stream into an Java object. Properties
+ * available for this type are: {@link #getMethod()}, {@link #getEntityGraph()}.
+ */
+ METHOD_READER,
+
+ /**
+ * Context created to process property accessors when writing entity to an output stream from an Java object. Properties
+ * available for this type are: {@link #getMethod()}, {@link #getEntityGraph()}.
+ */
+ METHOD_WRITER
+ }
+
+ /**
+ * Get the {@link Type type} of this context.
+ *
+ * @return entity processing context type.
+ */
+ public Type getType();
+
+ /**
+ * Get entity class to be processed. The entity class is available only for {@link Type#CLASS_WRITER} and
+ * {@link Type#CLASS_READER} context types.
+ *
+ * @return entity class or {@code null} if the class is not available.
+ */
+ public Class<?> getEntityClass();
+
+ /**
+ * Get field to be processed. The field is available only for {@link Type#PROPERTY_WRITER} and
+ * {@link Type#PROPERTY_READER} context types.
+ *
+ * @return field or {@code null} if the field is not available.
+ */
+ public Field getField();
+
+ /**
+ * Get method to be processed. The method is available for {@link Type#PROPERTY_WRITER}, {@link Type#PROPERTY_READER},
+ * {@link Type#METHOD_WRITER}, {@link Type#METHOD_READER} context types.
+ *
+ * @return method or {@code null} if the method is not available.
+ */
+ public Method getMethod();
+
+ /**
+ * Get entity graph to be modified by the processing. The entity graph is available for all context types.
+ *
+ * @return entity graph.
+ */
+ public EntityGraph getEntityGraph();
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/FilteringHelper.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/FilteringHelper.java
new file mode 100644
index 0000000..a0f2fa1
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/FilteringHelper.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering.spi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.security.AccessController;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.xml.bind.JAXBElement;
+
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.internal.util.collection.DataStructures;
+
+/**
+ * SPI utility methods for entity filtering.
+ *
+ * @author Michal Gajdos
+ */
+public final class FilteringHelper {
+
+ /**
+ * Empty annotation array.
+ */
+ public static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
+
+ private static final ConcurrentMap<Type, Class<?>> ENTITY_CLASSES = DataStructures.createConcurrentMap();
+
+ /**
+ * Determine whether given class is filterable by entity-filtering. Filterable classes are all classes that are not primitives
+ * and are not in a package matching <code>java.*</code>.
+ *
+ * @param clazz entity class to be examined.
+ * @return {@code true} whether the class is filterable, {@code false otherwise}.
+ */
+ public static boolean filterableEntityClass(final Class<?> clazz) {
+ return !ReflectionHelper.isPrimitive(clazz) && !clazz.getPackage().getName().startsWith("java.");
+ }
+
+ /**
+ * A convenience method to get the domain class (i.e. <i>Customer</i>) from the generic type (i.e. <i>Customer</i>,
+ * <i>List<Customer></i>, <i>JAXBElement<Customer></i>, <i>JAXBElement<? extends Customer></i>,
+ * <i>List<JAXBElement<Customer>></i>, or <i>List<JAXBElement<? extends Customer>></i>).
+ *
+ * @param genericType type to obtain entity domain class for.
+ * @return entity domain class.
+ */
+ public static Class<?> getEntityClass(final Type genericType) {
+ if (!ENTITY_CLASSES.containsKey(genericType)) {
+ ENTITY_CLASSES.putIfAbsent(genericType, _getEntityClass(genericType));
+ }
+ return ENTITY_CLASSES.get(genericType);
+ }
+
+ /**
+ * Note: This method was copied from {@code MOXyJsonProvider}.
+ */
+ @SuppressWarnings("JavaDoc")
+ private static Class<?> _getEntityClass(final Type genericType) {
+ if (null == genericType) {
+ return Object.class;
+ }
+ if (genericType instanceof Class && genericType != JAXBElement.class) {
+ final Class<?> clazz = (Class<?>) genericType;
+ if (clazz.isArray()) {
+ return _getEntityClass(clazz.getComponentType());
+ }
+ return clazz;
+ } else if (genericType instanceof ParameterizedType) {
+ final ParameterizedType parameterizedType = (ParameterizedType) genericType;
+ final Type[] arguments = parameterizedType.getActualTypeArguments();
+
+ final Type type = parameterizedType.getRawType() == Map.class ? arguments[1] : arguments[0];
+ if (type instanceof ParameterizedType) {
+ final Type rawType = ((ParameterizedType) type).getRawType();
+ if (rawType == JAXBElement.class) {
+ return _getEntityClass(type);
+ }
+ } else if (type instanceof WildcardType) {
+ final Type[] upperTypes = ((WildcardType) type).getUpperBounds();
+ if (upperTypes.length > 0) {
+ final Type upperType = upperTypes[0];
+ if (upperType instanceof Class) {
+ return (Class<?>) upperType;
+ }
+ }
+ } else if (JAXBElement.class == type
+ || type instanceof TypeVariable) {
+ return Object.class;
+ }
+ //noinspection ConstantConditions
+ return (Class<?>) type;
+ } else if (genericType instanceof GenericArrayType) {
+ final GenericArrayType genericArrayType = (GenericArrayType) genericType;
+ return _getEntityClass(genericArrayType.getGenericComponentType());
+ } else {
+ return Object.class;
+ }
+ }
+
+ /**
+ * Get accessor method mappings (field -> getter/setter) for given class.
+ *
+ * @param clazz entity class to obtain property methods for.
+ * @param isGetter flag determining whether to look for getters or setters.
+ * @return non-null map of field-accessor mappings.
+ */
+ public static Map<String, Method> getPropertyMethods(final Class<?> clazz, final boolean isGetter) {
+ final Map<String, Method> methods = new HashMap<>();
+
+ for (final Method method : AccessController.doPrivileged(ReflectionHelper.getDeclaredMethodsPA(clazz))) {
+ if ((isGetter && ReflectionHelper.isGetter(method))
+ || (!isGetter && ReflectionHelper.isSetter(method))) {
+
+ methods.put(ReflectionHelper.getPropertyName(method), method);
+ }
+ }
+
+ final Class<?> parent = clazz.getSuperclass();
+ // We're interested in fields/accessors in superclasses but not those from i.e. Object/Enum.
+ if (parent != null && !parent.getPackage().getName().startsWith("java.lang")) {
+ methods.putAll(getPropertyMethods(parent, isGetter));
+ }
+
+ return methods;
+ }
+
+ /**
+ * Get a set containing default filtering scope.
+ *
+ * @return default filtering scope.
+ */
+ public static Set<String> getDefaultFilteringScope() {
+ return Collections.singleton(ScopeProvider.DEFAULT_SCOPE);
+ }
+
+ /**
+ * Prevent instantiation.
+ */
+ private FilteringHelper() {
+ }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectGraph.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectGraph.java
new file mode 100644
index 0000000..99f326e
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectGraph.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering.spi;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Read-only graph containing representations of an entity class that should be processed in entity-filtering. The
+ * representations are twofolds: simple (primitive/non-filterable) fields and further-filterable fields (represented by
+ * subgraphs).
+ * <p/>
+ * Object graph instances are created for entity-filtering scopes that are obtained from entity annotations, configuration or
+ * scopes defined on resource methods / classes (on server).
+ *
+ * @author Michal Gajdos
+ * @see ObjectGraphTransformer
+ * @see ScopeResolver
+ */
+public interface ObjectGraph {
+
+ /**
+ * Get entity domain class of this graph.
+ *
+ * @return entity domain class.
+ */
+ public Class<?> getEntityClass();
+
+ /**
+ * Get a set of all simple (non-filterable) fields of entity class. Value of each of these fields is either primitive or
+ * the entity-filtering feature cannot be applied to this field. Values of these fields can be directly processed.
+ *
+ * @return non-filterable fields.
+ */
+ public Set<String> getFields();
+
+ /**
+ * Get fields with the given parent path. The parent path, which may exist in the requested filtering scopes, is
+ * used for context to match against the field at the subgraph level.
+ *
+ * @param parent name of parent field.
+ * @return non-filterable fields.
+ */
+ public Set<String> getFields(String parent);
+
+ /**
+ * Get a map of all further-filterable fields of entity class. Mappings are represented as:
+ * <pre>
+ * <field> -> <object-graph></pre>
+ * It is supposed that object graphs contained in this map would be processed further.
+ *
+ * @return further-filterable map of fields.
+ */
+ public Map<String, ObjectGraph> getSubgraphs();
+
+ /**
+ * Get subgraphs with the given parent path. The parent path, which may exist in the requested filtering scopes, is
+ * used for context to match against the subgraph level.
+ *
+ * @param parent name of parent field.
+ * @return further-filterable map of fields.
+ *
+ */
+ public Map<String, ObjectGraph> getSubgraphs(String parent);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectGraphTransformer.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectGraphTransformer.java
new file mode 100644
index 0000000..f3261c7
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectGraphTransformer.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering.spi;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * This contract brings support for transforming an internal representation of entity data filtering feature into an object
+ * familiar to underlying provider (e.g. message body worker).
+ * <p>
+ * This interface is supposed to be implemented by modules providing JAX-RS/Jersey providers / configuration object (e.g. message
+ * body workers) that can directly affect reading/writing of an entity.
+ * </p>
+ * <p>
+ * Implementations should be registered into client/server runtime via
+ * {@link AbstractBinder jersey binder} (for more information and common implementation see
+ * {@link AbstractObjectProvider}):
+ * <pre>
+ * bindAsContract(MyObjectGraphTransformer.class)
+ * // FilteringGraphTransformer.
+ * .to(new TypeLiteral<ObjectGraphTransformer<MyFilteringObject>>() {})
+ * // Scope.
+ * .in(Singleton.class);
+ * </pre>
+ * The custom transformer can be then {@link javax.inject.Inject injected} as one these injection point types:
+ * <ul>
+ * <li>{@code MyObjectGraphTransformer}</li>
+ * <li>{@code javax.inject.Provider<ObjectGraphTransformer<MyFilteringObject>>}</li>
+ * </ul>
+ * </p>
+ * <p>
+ * By default a {@code ObjectGraph} -> {@code ObjectGraph} transformer is available in the runtime. This transformer can be
+ * injected (via {@link javax.inject.Inject @Inject}) into the following types:
+ * <ul>
+ * <li>{@code ObjectGraphTransformer}</li>
+ * <li>{@code javax.inject.Provider<ObjectGraphTransformer<Object>>}</li>
+ * <li>{@code javax.inject.Provider<ObjectGraphTransformer<ObjectGraph>>}</li>
+ * </ul>
+ * </p>
+ *
+ * @param <T> representation of entity data filtering requested by provider.
+ * @author Michal Gajdos
+ * @see AbstractObjectProvider
+ * @see ObjectProvider
+ */
+@Contract
+public interface ObjectGraphTransformer<T> {
+
+ /**
+ * Transform a given graph into an entity-filtering object. The entire graph (incl. it's subgraphs) should be processed by
+ * this method as this method is invoked only once for a root entity class.
+ *
+ * @param graph object graph to be transformed.
+ * @return entity-filtering object requested by provider.
+ */
+ public T transform(final ObjectGraph graph);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectProvider.java
new file mode 100644
index 0000000..f22e77d
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectProvider.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering.spi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Entry point of Entity Data Filtering feature for providers working with custom entities and media types (reading/writing).
+ * Exposed methods are supposed to provide an entity-filtering object of defined type (generic parameter type
+ * {@code <T>}) for given types/classes that is requested by underlying provider (e.g. message body worker).
+ * <p>
+ * Methods are also accepting a list of entity annotations which entity-filtering scopes and then particular entity-filtering
+ * object are determined from. Entity annotations can be passed to the runtime via:
+ * <ul>
+ * <li>{@link javax.ws.rs.client.Entity#entity(Object, javax.ws.rs.core.MediaType, java.lang.annotation.Annotation[])} on the
+ * client, or</li>
+ * <li>{@link javax.ws.rs.core.Response.ResponseBuilder#entity(Object, java.lang.annotation.Annotation[])} on the server</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Custom implementations should, during processing, look up for available {@link EntityProcessor entity processors} to examine
+ * given entity classes and {@link ScopeResolver scope providers} to determine the current entity-filtering scope. Entity class
+ * and entity-filtering scopes determine the {@link ObjectGraph object graph} passed to {@link ObjectGraphTransformer object graph
+ * transformer} and hence the resulting entity-filtering object.
+ * </p>
+ * <p>
+ * Implementations should be registered into client/server runtime via
+ * {@link AbstractBinder jersey binder} (for more information and common implementation see
+ * {@link AbstractObjectProvider}):
+ * <pre>
+ * bindAsContract(MyObjectProvider.class)
+ * // FilteringGraphTransformer.
+ * .to(new TypeLiteral<ObjectGraphTransformer<MyFilteringObject>>() {})
+ * // Scope.
+ * .in(Singleton.class);
+ * </pre>
+ * The custom provider can be then {@link javax.inject.Inject injected} as one these injection point types:
+ * <ul>
+ * <li>{@code MyObjectProvider}</li>
+ * <li>{@code javax.inject.Provider<ObjectProvider<MyFilteringObject>>}</li>
+ * </ul>
+ * </p>
+ * <p>
+ * By default a {@code ObjectGraph} provider is available in the runtime. This object provider can be injected (via
+ * {@link javax.inject.Inject @Inject}) into the following types:
+ * <ul>
+ * <li>{@code ObjectProvider}</li>
+ * <li>{@code javax.inject.Provider<ObjectProvider<Object>>}</li>
+ * <li>{@code javax.inject.Provider<ObjectProvider<ObjectGraph>>}</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Note: For most of the cases it is sufficient that users implement {@link ObjectGraphTransformer object graph transformer} by
+ * extending {@link AbstractObjectProvider} class.
+ * </p>
+ *
+ * @param <T> representation of entity data filtering requested by provider.
+ * @author Michal Gajdos
+ * @see AbstractObjectProvider
+ * @see ObjectGraphTransformer
+ */
+@Contract
+public interface ObjectProvider<T> {
+
+ /**
+ * Get reader/writer entity-filtering object for given type.
+ *
+ * @param genericType type for which the object is requested.
+ * @param forWriter flag to determine whether to create object for reading/writing purposes.
+ * @param annotations entity annotations to determine the runtime scope.
+ * @return entity-filtering object.
+ */
+ public T getFilteringObject(Type genericType, boolean forWriter, final Annotation... annotations);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ScopeProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ScopeProvider.java
new file mode 100644
index 0000000..9bd5c11
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ScopeProvider.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering.spi;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import org.glassfish.jersey.message.filtering.EntityFiltering;
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Entry point for obtaining entity-filtering scopes used to process a request/response entity. Entity-filtering scopes are
+ * obtained from (sorted by priority):
+ * <ul>
+ * <li>entity annotations - provided with entity when creating request/response</li>
+ * <li>annotations stored under
+ * {@value org.glassfish.jersey.message.filtering.EntityFilteringFeature#ENTITY_FILTERING_SCOPE} property obtained from
+ * {@link javax.security.auth.login.Configuration configuration}
+ * </li>
+ * <li>entity-filtering annotations present on resource methods/classes (on server)</li>
+ * </ul>
+ * <p/>
+ * Note: Definition of entity-filtering scopes can be found in {@link ScopeResolver}.
+ *
+ * @author Michal Gajdos
+ * @see ScopeResolver
+ */
+@Contract
+public interface ScopeProvider {
+
+ /**
+ * Default entity-filtering scope.
+ * <p/>
+ * Default scope is used in {@link ObjectGraph object graph} to retrieve a subgraph instance at the moment subgraph's entity
+ * class does not define any entity-filtering scope the object graph was created for.
+ * <p/>
+ * This scope is created for an {@link EntityGraph entity graph} if no other entity-filtering / security annotation is present
+ * on a class.
+ */
+ public static final String DEFAULT_SCOPE = EntityFiltering.class.getName();
+
+ /**
+ * Get entity-filtering scopes to be used to process an entity.
+ *
+ * @param entityAnnotations entity annotations provided with entity when creating request/response.
+ * @param defaultIfNotFound flag determining whether the default entity-filtering scope should be returned if no other
+ * scope can be obtained.
+ * @return non-null entity-filtering scopes.
+ */
+ public Set<String> getFilteringScopes(final Annotation[] entityAnnotations, final boolean defaultIfNotFound);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ScopeResolver.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ScopeResolver.java
new file mode 100644
index 0000000..c1403d2
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ScopeResolver.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering.spi;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Class used to resolve entity-filtering scopes from annotations. Annotations passed to {@code #resolve()} method
+ * can be one of the following: entity annotations (provided when creating request/response entity),
+ * annotations obtained from {@link javax.ws.rs.core.Configuration configuration}, resource method / resource class annotations.
+ * <p/>
+ * Entity-filtering scope is supposed to be an unique string that can be derived from an annotations and that can be further used
+ * in internal entity data filtering structures. Examples of such unique strings are:
+ * <ul>
+ * <li><code>@MyDetailedView</code> -> <code>my.package.MyDetailedView</code></li>
+ * <li>
+ * <code>@RolesAllowed({"manager", "user"})</code> -> <code>javax.annotation.security.RolesAllowed_manager</code> and
+ * <code>javax.annotation.security.RolesAllowed_user</code>
+ * </li>
+ * </ul>
+ * <p/>
+ * {@link ScopeResolver Scope resolvers} are invoked from {@link ScopeProvider scope provider} instance.
+ *
+ * @author Michal Gajdos
+ */
+@Contract
+public interface ScopeResolver {
+
+ /**
+ * Resolve entity-filtering scopes for given annotations.
+ *
+ * @param annotations list of arbitrary annotations.
+ * @return non-null set of entity-filtering scopes.
+ */
+ public Set<String> resolve(final Annotation[] annotations);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/package-info.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/package-info.java
new file mode 100644
index 0000000..d10dba7
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/package-info.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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
+ */
+
+/**
+ * SPI for Entity Data Filtering in Jersey.
+ * <p/>
+ * To create a custom entity-filtering annotation with special handling (e.g. field aggregator annotation used to annotate
+ * classes), refer to:
+ * <ul>
+ * <li>{@link org.glassfish.jersey.message.filtering.spi.EntityProcessor}</li>
+ * <li>{@link org.glassfish.jersey.message.filtering.spi.AbstractEntityProcessor}</li>
+ * <li>{@link org.glassfish.jersey.message.filtering.spi.ScopeResolver}</li>
+ * </ul>
+ * <p/>
+ * To support Entity Data Filtering in custom providers (e.g. message body workers), refer to:
+ * <ul>
+ * <li>{@link org.glassfish.jersey.message.filtering.spi.ObjectProvider}</li>
+ * <li>{@link org.glassfish.jersey.message.filtering.spi.ObjectGraphTransformer}</li>
+ * <li>{@link org.glassfish.jersey.message.filtering.spi.AbstractObjectProvider}</li>
+ * </ul>
+ * <p/>
+ *
+ * @since 2.3
+ */
+package org.glassfish.jersey.message.filtering.spi;
diff --git a/ext/entity-filtering/src/main/resources/org/glassfish/jersey/message/filtering/internal/localization.properties b/ext/entity-filtering/src/main/resources/org/glassfish/jersey/message/filtering/internal/localization.properties
new file mode 100644
index 0000000..0656696
--- /dev/null
+++ b/ext/entity-filtering/src/main/resources/org/glassfish/jersey/message/filtering/internal/localization.properties
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2013, 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
+#
+
+# {0} - value of the property
+entity.filtering.scope.not.annotations=The entity-filtering scope property does not contain an instance of Annotation or Annotation[] ({0}).
+merging.filtering.scopes=Merging two sets of entity-filtering scopes obtained from two different scope resolvers.
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/DenyAllEntity.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/DenyAllEntity.java
new file mode 100644
index 0000000..94922c6
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/DenyAllEntity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import javax.annotation.security.DenyAll;
+
+/**
+ * @author Michal Gajdos
+ */
+@DenyAll
+public class DenyAllEntity {
+
+ private String field;
+
+ @DenyAll
+ public String getField() {
+ return field;
+ }
+
+ @DenyAll
+ public void setField(final String field) {
+ this.field = field;
+ }
+
+ @DenyAll
+ public SubEntity getSubgraph() {
+ return null;
+ }
+
+ @DenyAll
+ public void setSubgraph(final SubEntity subgraph) {
+ }
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/PermitAllEntity.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/PermitAllEntity.java
new file mode 100644
index 0000000..1a95adb
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/PermitAllEntity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import javax.annotation.security.PermitAll;
+
+/**
+ * @author Michal Gajdos
+ */
+@PermitAll
+public class PermitAllEntity {
+
+ private String field;
+
+ @PermitAll
+ public String getField() {
+ return field;
+ }
+
+ @PermitAll
+ public void setField(final String field) {
+ this.field = field;
+ }
+
+ @PermitAll
+ public SubEntity getSubgraph() {
+ return null;
+ }
+
+ @PermitAll
+ public void setSubgraph(final SubEntity subgraph) {
+ }
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/RolesAllowedEntity.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/RolesAllowedEntity.java
new file mode 100644
index 0000000..f82ef9d
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/RolesAllowedEntity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import javax.annotation.security.RolesAllowed;
+
+/**
+ * @author Michal Gajdos
+ */
+@RolesAllowed({"manager", "client"})
+public class RolesAllowedEntity {
+
+ private String field;
+
+ @RolesAllowed("manager")
+ public String getField() {
+ return field;
+ }
+
+ @RolesAllowed("client")
+ public void setField(final String field) {
+ this.field = field;
+ }
+
+ @RolesAllowed("manager")
+ public SubEntity getSubgraph() {
+ return null;
+ }
+
+ @RolesAllowed("client")
+ public void setSubgraph(final SubEntity subgraph) {
+ }
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityAnnotationsTest.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityAnnotationsTest.java
new file mode 100644
index 0000000..8c5fa49
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityAnnotationsTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * {@link org.glassfish.jersey.message.filtering.SecurityAnnotations} unit tests.
+ *
+ * @author Michal Gajdos
+ */
+public class SecurityAnnotationsTest {
+
+ @Test
+ public void testCreateRolesAllowed() throws Exception {
+ assertThat(new String[] {"manager", "user"}, equalTo(SecurityAnnotations.rolesAllowed("manager", "user").value()));
+ }
+
+ @Test
+ public void testCreateRolesAllowedNegative() throws Exception {
+ assertThat(new String[] {}, equalTo(SecurityAnnotations.rolesAllowed((String) null).value()));
+ assertThat(new String[] {"manager"}, equalTo(SecurityAnnotations.rolesAllowed("manager", null).value()));
+ }
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityEntityProcessorTest.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityEntityProcessorTest.java
new file mode 100644
index 0000000..21edda4
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityEntityProcessorTest.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessor;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessorContext;
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+import org.glassfish.jersey.message.filtering.spi.ScopeProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * {@link org.glassfish.jersey.message.filtering.SecurityEntityProcessor} unit tests.
+ *
+ * @author Michal Gajdos
+ */
+@SuppressWarnings("JavaDoc")
+public class SecurityEntityProcessorTest {
+
+ private SecurityEntityProcessor processor;
+
+ @Before
+ public void setUp() throws Exception {
+ processor = new SecurityEntityProcessor();
+ }
+
+ @Test
+ public void testProcessPermitAllClass() throws Exception {
+ final EntityGraph actual = new EntityGraphImpl(PermitAllEntity.class);
+
+ final EntityGraph expected = new EntityGraphImpl(PermitAllEntity.class);
+ expected.addFilteringScopes(FilteringHelper.getDefaultFilteringScope());
+
+ for (final boolean forWriter : new boolean[] {true, false}) {
+ final EntityProcessor.Result result = testProcessClass(PermitAllEntity.class, actual, forWriter);
+
+ assertThat(result, equalTo(EntityProcessor.Result.APPLY));
+ assertThat(actual, equalTo(expected));
+ }
+ }
+
+ @Test
+ public void testProcessDenyAllClass() throws Exception {
+ final EntityGraph actual = new EntityGraphImpl(DenyAllEntity.class);
+ final EntityGraph expected = new EntityGraphImpl(DenyAllEntity.class);
+
+ for (final boolean forWriter : new boolean[] {true, false}) {
+ final EntityProcessor.Result result = testProcessClass(DenyAllEntity.class, actual, forWriter);
+
+ assertThat(result, equalTo(EntityProcessor.Result.ROLLBACK));
+ assertThat(actual, equalTo(expected));
+ }
+ }
+
+ @Test
+ public void testProcessRolesAllowedClass() throws Exception {
+ final EntityGraph actual = new EntityGraphImpl(RolesAllowedEntity.class);
+
+ final EntityGraph expected = new EntityGraphImpl(RolesAllowedEntity.class);
+ expected.addFilteringScopes(
+ Arrays.asList(
+ SecurityHelper.getRolesAllowedScope("manager"), SecurityHelper.getRolesAllowedScope("client"))
+ .stream()
+ .collect(Collectors.toSet()));
+
+ for (final boolean forWriter : new boolean[] {true, false}) {
+ final EntityProcessor.Result result = testProcessClass(RolesAllowedEntity.class, actual, forWriter);
+
+ assertThat(result, equalTo(EntityProcessor.Result.APPLY));
+ assertThat(actual, equalTo(expected));
+ }
+ }
+
+ private EntityProcessor.Result testProcessClass(final Class<?> clazz, final EntityGraph graph, final boolean forWriter)
+ throws Exception {
+
+ final EntityProcessorContext context = new EntityProcessorContextImpl(
+ forWriter ? EntityProcessorContext.Type.CLASS_WRITER : EntityProcessorContext.Type.CLASS_READER,
+ clazz, graph);
+
+ return processor.process(context);
+ }
+
+ @Test
+ public void testProcessPermitAllProperties() throws Exception {
+ final EntityGraph actual = new EntityGraphImpl(PermitAllEntity.class);
+
+ final EntityGraph expected = new EntityGraphImpl(PermitAllEntity.class);
+ expected.addField("field", ScopeProvider.DEFAULT_SCOPE);
+
+ for (final boolean forWriter : new boolean[] {true, false}) {
+ final EntityProcessor.Result result = testProcessProperty(PermitAllEntity.class, actual, forWriter);
+
+ assertThat(result, equalTo(EntityProcessor.Result.APPLY));
+ assertThat(actual, equalTo(expected));
+ }
+ }
+
+ @Test
+ public void testProcessDenyAllProperties() throws Exception {
+ final EntityGraph actual = new EntityGraphImpl(DenyAllEntity.class);
+ final EntityGraph expected = new EntityGraphImpl(DenyAllEntity.class);
+
+ for (final boolean forWriter : new boolean[] {true, false}) {
+ final EntityProcessor.Result result = testProcessProperty(DenyAllEntity.class, actual, forWriter);
+
+ assertThat(result, equalTo(EntityProcessor.Result.ROLLBACK));
+ assertThat(actual, equalTo(expected));
+ }
+ }
+
+ @Test
+ public void testProcessRolesAllowedProperties() throws Exception {
+ final EntityGraph actual = new EntityGraphImpl(RolesAllowedEntity.class);
+ final EntityGraph expected = new EntityGraphImpl(RolesAllowedEntity.class);
+
+ for (final boolean forWriter : new boolean[] {true, false}) {
+ final EntityProcessor.Result result = testProcessProperty(RolesAllowedEntity.class, actual, forWriter);
+
+ if (forWriter) {
+ expected.addField("field", SecurityHelper.getRolesAllowedScope("manager"));
+ } else {
+ expected.addField("field", SecurityHelper.getRolesAllowedScope("client"));
+ }
+
+ assertThat(result, equalTo(EntityProcessor.Result.APPLY));
+ assertThat(actual, equalTo(expected));
+ }
+ }
+
+ private EntityProcessor.Result testProcessProperty(final Class<?> clazz, final EntityGraph graph, final boolean forWriter)
+ throws Exception {
+
+ final Field field = clazz.getDeclaredField("field");
+ final Method method = forWriter ? clazz.getMethod("getField") : clazz.getMethod("setField", String.class);
+
+ final EntityProcessorContext context = new EntityProcessorContextImpl(
+ forWriter ? EntityProcessorContext.Type.PROPERTY_WRITER : EntityProcessorContext.Type.PROPERTY_WRITER,
+ field, method, graph);
+
+ return processor.process(context);
+ }
+
+ @Test
+ public void testProcessPermitAllAccessors() throws Exception {
+ final EntityGraph actual = new EntityGraphImpl(PermitAllEntity.class);
+ actual.addFilteringScopes(FilteringHelper.getDefaultFilteringScope());
+
+ final EntityGraph expected = new EntityGraphImpl(PermitAllEntity.class);
+ expected.addFilteringScopes(FilteringHelper.getDefaultFilteringScope());
+ expected.addSubgraph("subgraph", SubEntity.class, ScopeProvider.DEFAULT_SCOPE);
+
+ for (final boolean forWriter : new boolean[] {true, false}) {
+ final EntityProcessor.Result result = testProcessAccessor(PermitAllEntity.class, actual, forWriter);
+
+ assertThat(result, equalTo(EntityProcessor.Result.APPLY));
+ assertThat(actual, equalTo(expected));
+ }
+ }
+
+ @Test
+ public void testProcessDenyAllAccessors() throws Exception {
+ final EntityGraph actual = new EntityGraphImpl(DenyAllEntity.class);
+ final EntityGraph expected = new EntityGraphImpl(DenyAllEntity.class);
+
+ for (final boolean forWriter : new boolean[] {true, false}) {
+ final EntityProcessor.Result result = testProcessAccessor(DenyAllEntity.class, actual, forWriter);
+
+ assertThat(result, equalTo(EntityProcessor.Result.ROLLBACK));
+ assertThat(actual, equalTo(expected));
+ }
+ }
+
+ @Test
+ public void testProcessRolesAllowedAccessor() throws Exception {
+ final EntityGraph actual = new EntityGraphImpl(RolesAllowedEntity.class);
+ final EntityGraph expected = new EntityGraphImpl(RolesAllowedEntity.class);
+
+ for (final boolean forWriter : new boolean[] {true, false}) {
+ final EntityProcessor.Result result = testProcessAccessor(RolesAllowedEntity.class, actual, forWriter);
+
+ if (forWriter) {
+ expected.addSubgraph("subgraph", SubEntity.class, SecurityHelper.getRolesAllowedScope("manager"));
+ } else {
+ expected.addSubgraph("subgraph", SubEntity.class, SecurityHelper.getRolesAllowedScope("client"));
+ }
+
+ assertThat(result, equalTo(EntityProcessor.Result.APPLY));
+ assertThat(actual, equalTo(expected));
+ }
+ }
+
+ private EntityProcessor.Result testProcessAccessor(final Class<?> clazz, final EntityGraph graph, final boolean forWriter)
+ throws Exception {
+
+ final Method method = forWriter ? clazz.getMethod("getSubgraph") : clazz.getMethod("setSubgraph", SubEntity.class);
+
+ final EntityProcessorContext context = new EntityProcessorContextImpl(
+ forWriter ? EntityProcessorContext.Type.PROPERTY_WRITER : EntityProcessorContext.Type.PROPERTY_WRITER,
+ method, graph);
+
+ return processor.process(context);
+ }
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityHelperTest.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityHelperTest.java
new file mode 100644
index 0000000..e86b1d3
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityHelperTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.SecurityContext;
+
+import javax.annotation.security.RolesAllowed;
+
+import org.glassfish.jersey.internal.inject.CustomAnnotationLiteral;
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * {@link org.glassfish.jersey.message.filtering.SecurityHelper} unit tests.
+ *
+ * @author Michal Gajdos
+ */
+public class SecurityHelperTest {
+
+ @Test
+ public void testFilteringScopes() throws Exception {
+ Annotation[] annotations;
+ Set<String> expected;
+
+ // Empty annotations.
+ annotations = new Annotation[0];
+ assertThat(SecurityHelper.getFilteringScopes(annotations), equalTo(Collections.<String>emptySet()));
+
+ // Not security annotations.
+ annotations = new Annotation[] {CustomAnnotationLiteral.INSTANCE, CustomAnnotationLiteral.INSTANCE};
+ assertThat(SecurityHelper.getFilteringScopes(annotations), equalTo(Collections.<String>emptySet()));
+
+ // Mixed.
+ annotations = new Annotation[] {CustomAnnotationLiteral.INSTANCE, SecurityAnnotations
+ .rolesAllowed("manager"), CustomAnnotationLiteral.INSTANCE};
+ expected = Collections.singleton(RolesAllowed.class.getName() + "_manager");
+ assertThat(SecurityHelper.getFilteringScopes(annotations), equalTo(expected));
+
+ // Multiple.
+ annotations = new Annotation[] {SecurityAnnotations.rolesAllowed("manager", "user")};
+ expected = Arrays.asList(RolesAllowed.class.getName() + "_manager", RolesAllowed.class.getName() + "_user")
+ .stream()
+ .collect(Collectors.toSet());
+ assertThat(SecurityHelper.getFilteringScopes(annotations), equalTo(expected));
+
+ // PermitAll weirdo.
+ annotations = new Annotation[] {SecurityAnnotations.permitAll()};
+ assertThat(SecurityHelper.getFilteringScopes(annotations), equalTo(FilteringHelper.getDefaultFilteringScope()));
+
+ // DenyAll weirdo.
+ annotations = new Annotation[] {SecurityAnnotations.denyAll()};
+ assertThat(SecurityHelper.getFilteringScopes(annotations), equalTo(null));
+ }
+
+ @Test
+ public void testFilteringScopesWithContext() throws Exception {
+ final SecurityContext context = new TestSecurityContext();
+
+ Annotation[] annotations;
+ Set<String> expected;
+
+ // Empty annotations.
+ annotations = new Annotation[0];
+ assertThat(SecurityHelper.getFilteringScopes(context, annotations), equalTo(Collections.<String>emptySet()));
+
+ // Not security annotations.
+ annotations = new Annotation[] {CustomAnnotationLiteral.INSTANCE, CustomAnnotationLiteral.INSTANCE};
+ assertThat(SecurityHelper.getFilteringScopes(context, annotations), equalTo(Collections.<String>emptySet()));
+
+ // Mixed.
+ annotations = new Annotation[] {CustomAnnotationLiteral.INSTANCE, SecurityAnnotations
+ .rolesAllowed("manager"), CustomAnnotationLiteral.INSTANCE};
+ expected = Collections.singleton(RolesAllowed.class.getName() + "_manager");
+ assertThat(SecurityHelper.getFilteringScopes(context, annotations), equalTo(expected));
+
+ // Multiple.
+ annotations = new Annotation[] {SecurityAnnotations.rolesAllowed("client", "user")};
+ expected = Collections.singleton(RolesAllowed.class.getName() + "_user");
+ assertThat(SecurityHelper.getFilteringScopes(context, annotations), equalTo(expected));
+
+ // PermitAll weirdo.
+ annotations = new Annotation[] {SecurityAnnotations.permitAll()};
+ assertThat(SecurityHelper.getFilteringScopes(context, annotations), equalTo(FilteringHelper.getDefaultFilteringScope()));
+
+ // DenyAll weirdo.
+ annotations = new Annotation[] {SecurityAnnotations.denyAll()};
+ assertThat(SecurityHelper.getFilteringScopes(context, annotations), equalTo(null));
+ }
+
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SubEntity.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SubEntity.java
new file mode 100644
index 0000000..e874eb9
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SubEntity.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2013, 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.message.filtering;
+
+/**
+ * @author Michal Gajdos
+ */
+public class SubEntity {
+
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/TestSecurityContext.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/TestSecurityContext.java
new file mode 100644
index 0000000..1e75312
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/TestSecurityContext.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.message.filtering;
+
+import java.security.Principal;
+
+import javax.ws.rs.core.SecurityContext;
+
+/**
+* @author Michal Gajdos
+*/
+class TestSecurityContext implements SecurityContext {
+
+ @Override
+ public Principal getUserPrincipal() {
+ return new Principal() {
+ @Override
+ public String getName() {
+ return "";
+ }
+ };
+ }
+
+ @Override
+ public boolean isUserInRole(final String role) {
+ return "manager".equals(role) || "user".equals(role);
+ }
+
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ return null;
+ }
+}
diff --git a/ext/metainf-services/pom.xml b/ext/metainf-services/pom.xml
new file mode 100644
index 0000000..8eef68a
--- /dev/null
+++ b/ext/metainf-services/pom.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<!--
+
+ 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
+
+-->
+
+<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.ext</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-metainf-services</artifactId>
+ <name>jersey-ext-metainf-services</name>
+
+ <description>
+ Jersey extension module enabling automatic registration of JAX-RS providers (MBW/MBR/EM) via META-INF/services mechanism.
+ </description>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <!--
+ Excluding only *.tests.* packages for this module. At least one class has to exist in non-excluded
+ packages to generate proper -javadoc.jar file. See https://jira.codehaus.org/browse/MJAVADOC-329
+ -->
+ <excludePackageNames>*.tests.*</excludePackageNames>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <inherited>false</inherited>
+ <configuration>
+ <source>${java.version}</source>
+ <target>${java.version}</target>
+ <compilerArguments>
+ <!-- Do not warn about using sun.misc.Unsafe -->
+ <XDignore.symbol.file />
+ </compilerArguments>
+ <showWarnings>false</showWarnings>
+ <fork>false</fork>
+ </configuration>
+ </plugin>
+ <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>
+ <extensions>true</extensions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ <version>${project.version}</version>
+ </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>
+ </dependencies>
+</project>
diff --git a/ext/metainf-services/src/main/java/org/glassfish/jersey/spidiscovery/internal/MetaInfServicesAutoDiscoverable.java b/ext/metainf-services/src/main/java/org/glassfish/jersey/spidiscovery/internal/MetaInfServicesAutoDiscoverable.java
new file mode 100644
index 0000000..118acd3
--- /dev/null
+++ b/ext/metainf-services/src/main/java/org/glassfish/jersey/spidiscovery/internal/MetaInfServicesAutoDiscoverable.java
@@ -0,0 +1,56 @@
+/*
+ * 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.spidiscovery.internal;
+
+import java.util.Map;
+
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.FeatureContext;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+
+import javax.annotation.Priority;
+
+import org.glassfish.jersey.internal.ServiceFinderBinder;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.spi.AutoDiscoverable;
+import org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable;
+
+/**
+ * @author Michal Gajdos
+ */
+@Priority(AutoDiscoverable.DEFAULT_PRIORITY)
+public class MetaInfServicesAutoDiscoverable implements ForcedAutoDiscoverable {
+
+ @Override
+ public void configure(final FeatureContext context) {
+ final Map<String, Object> properties = context.getConfiguration().getProperties();
+ final RuntimeType runtimeType = context.getConfiguration().getRuntimeType();
+
+ context.register(new AbstractBinder() {
+ @Override
+ protected void configure() {
+ // Message Body providers.
+ install(new ServiceFinderBinder<MessageBodyReader>(MessageBodyReader.class, properties, runtimeType));
+ install(new ServiceFinderBinder<MessageBodyWriter>(MessageBodyWriter.class, properties, runtimeType));
+ // Exception Mappers.
+ install(new ServiceFinderBinder<ExceptionMapper>(ExceptionMapper.class, properties, runtimeType));
+ }
+ });
+ }
+}
diff --git a/ext/metainf-services/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable b/ext/metainf-services/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable
new file mode 100644
index 0000000..f4c3e99
--- /dev/null
+++ b/ext/metainf-services/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable
@@ -0,0 +1 @@
+org.glassfish.jersey.spidiscovery.internal.MetaInfServicesAutoDiscoverable
diff --git a/ext/metainf-services/src/test/java/org/glassfish/jersey/message/MetaInfServicesTest.java b/ext/metainf-services/src/test/java/org/glassfish/jersey/message/MetaInfServicesTest.java
new file mode 100644
index 0000000..e764ef5
--- /dev/null
+++ b/ext/metainf-services/src/test/java/org/glassfish/jersey/message/MetaInfServicesTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.message;
+
+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.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.message.internal.ReaderWriter;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * @author Michal Gajdos
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({MetaInfServicesTest.Enable.class, MetaInfServicesTest.DisableServer.class,
+ MetaInfServicesTest.DisableClient.class})
+public class MetaInfServicesTest {
+
+ public static class MetaInf {
+
+ private String value;
+
+ public MetaInf(final String value) {
+ this.value = value;
+ }
+ }
+
+ @Path("resource")
+ public static class MetaInfServicesResource {
+
+ @POST
+ public MetaInf post(final MetaInf entity) {
+ return entity;
+ }
+ }
+
+ public static class MessageProvider implements MessageBodyReader<MetaInf>, MessageBodyWriter<MetaInf> {
+
+ @Context
+ private Configuration config;
+
+ @Override
+ public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return true;
+ }
+
+ @Override
+ public MetaInf readFrom(final Class<MetaInf> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType, final MultivaluedMap<String, String> httpHeaders,
+ final InputStream entityStream) throws IOException, WebApplicationException {
+ return new MetaInf(ReaderWriter.readFromAsString(entityStream, mediaType)
+ + "_read_" + config.getRuntimeType().name());
+ }
+
+ @Override
+ public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return true;
+ }
+
+ @Override
+ public long getSize(final MetaInf s, final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(final MetaInf s, final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
+ final OutputStream entityStream) throws IOException, WebApplicationException {
+ entityStream.write((s.value + "_write_" + config.getRuntimeType().name()).getBytes());
+ }
+ }
+
+ public static class Enable extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ return new ResourceConfig(MetaInfServicesResource.class);
+ }
+
+ @Test
+ public void testEnable() throws Exception {
+ final Response response = target("resource").request().post(Entity.text(new MetaInf("foo")));
+
+ assertThat(response.getStatus(), is(200));
+ assertThat(response.readEntity(MetaInf.class).value,
+ is("foo_write_CLIENT_read_SERVER_write_SERVER_read_CLIENT"));
+ }
+ }
+
+ public static class DisableServer extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ return new ResourceConfig(MetaInfServicesResource.class)
+ .property(ServerProperties.FEATURE_AUTO_DISCOVERY_DISABLE, true);
+ }
+
+ @Test
+ public void testDisableServer() throws Exception {
+ final Response response = target("resource").request().post(Entity.text(new MetaInf("foo")));
+
+ assertThat(response.getStatus(), is(200));
+ assertThat(response.readEntity(MetaInf.class).value,
+ is("foo_write_CLIENT_read_SERVER_write_SERVER_read_CLIENT"));
+ }
+ }
+
+ public static class DisableClient extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ return new ResourceConfig(MetaInfServicesResource.class);
+ }
+
+ @Override
+ protected void configureClient(final ClientConfig config) {
+ config.property(ClientProperties.FEATURE_AUTO_DISCOVERY_DISABLE, true);
+ }
+
+ @Test
+ public void testDisableServer() throws Exception {
+ final Response response = target("resource").request().post(Entity.text(new MetaInf("foo")));
+
+ assertThat(response.getStatus(), is(200));
+ assertThat(response.readEntity(MetaInf.class).value,
+ is("foo_write_CLIENT_read_SERVER_write_SERVER_read_CLIENT"));
+ }
+ }
+}
diff --git a/ext/metainf-services/src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader b/ext/metainf-services/src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader
new file mode 100644
index 0000000..5be0cee
--- /dev/null
+++ b/ext/metainf-services/src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader
@@ -0,0 +1 @@
+org.glassfish.jersey.message.MetaInfServicesTest$MessageProvider
\ No newline at end of file
diff --git a/ext/metainf-services/src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter b/ext/metainf-services/src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter
new file mode 100644
index 0000000..5be0cee
--- /dev/null
+++ b/ext/metainf-services/src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter
@@ -0,0 +1 @@
+org.glassfish.jersey.message.MetaInfServicesTest$MessageProvider
\ No newline at end of file
diff --git a/ext/mvc-bean-validation/pom.xml b/ext/mvc-bean-validation/pom.xml
new file mode 100644
index 0000000..4fa6f35
--- /dev/null
+++ b/ext/mvc-bean-validation/pom.xml
@@ -0,0 +1,65 @@
+<?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.ext</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-mvc-bean-validation</artifactId>
+ <name>jersey-ext-mvc-bean-validation</name>
+
+ <description>
+ Jersey extension module providing support for Bean Validation in MVC.
+ </description>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-mvc</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-bean-validation</artifactId>
+ <version>${project.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.el</groupId>
+ <artifactId>javax.el-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/MvcBeanValidationFeature.java b/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/MvcBeanValidationFeature.java
new file mode 100644
index 0000000..9927536
--- /dev/null
+++ b/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/MvcBeanValidationFeature.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2013, 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.mvc.beanvalidation;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.server.mvc.MvcFeature;
+import org.glassfish.jersey.server.validation.ValidationFeature;
+
+/**
+ * {@link Feature} used to add support for {@link MvcFeature MVC} and Bean Validation.
+ * <p/>
+ * {@link org.glassfish.jersey.server.mvc.Viewable Viewable} (template) defined by
+ * {@link org.glassfish.jersey.server.mvc.ErrorTemplate ErrorTemplate} annotation, present directly on an executed resource method
+ * or on a resource class the resource method is defined in, is processed to display an error message caused by an
+ * {@link javax.validation.ConstraintViolationException Bean Validation exception}. Model is, in this case, a list of
+ * {@link org.glassfish.jersey.server.validation.ValidationError validation errors}.
+ * <p/>
+ * Note: This feature also registers {@link MvcFeature}.
+ *
+ * @author Michal Gajdos
+ * @see org.glassfish.jersey.server.mvc.ErrorTemplate
+ * @since 2.3
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public class MvcBeanValidationFeature implements Feature {
+
+ @Override
+ public boolean configure(final FeatureContext context) {
+ final Configuration config = context.getConfiguration();
+
+ if (!config.isRegistered(ValidationErrorTemplateExceptionMapper.class)) {
+ // Exception Mapper.
+ context.register(ValidationErrorTemplateExceptionMapper.class);
+
+ // BeanValidation feature.
+ if (!config.isRegistered(ValidationFeature.class)) {
+ context.register(ValidationFeature.class);
+ }
+
+ // Mvc feature.
+ if (!config.isRegistered(MvcFeature.class)) {
+ context.register(MvcFeature.class);
+ }
+
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/ValidationErrorTemplateExceptionMapper.java b/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/ValidationErrorTemplateExceptionMapper.java
new file mode 100644
index 0000000..caba9cc
--- /dev/null
+++ b/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/ValidationErrorTemplateExceptionMapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2013, 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.mvc.beanvalidation;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+import javax.inject.Singleton;
+import javax.validation.ConstraintViolationException;
+
+import org.glassfish.jersey.server.mvc.spi.AbstractErrorTemplateMapper;
+import org.glassfish.jersey.server.validation.internal.ValidationHelper;
+
+/**
+ * {@link org.glassfish.jersey.spi.ExtendedExceptionMapper Exception mapper} providing validation errors that are passed to a
+ * viewable in case a Bean Validation exception has been thrown during processing an request.
+ *
+ * @author Michal Gajdos
+ * @since 2.3
+ */
+@Singleton
+final class ValidationErrorTemplateExceptionMapper extends AbstractErrorTemplateMapper<ConstraintViolationException> {
+
+ @Override
+ protected Response.Status getErrorStatus(final ConstraintViolationException cve) {
+ return ValidationHelper.getResponseStatus(cve);
+ }
+
+ @Override
+ protected Object getErrorModel(final ConstraintViolationException cve) {
+ return ValidationHelper.constraintViolationToValidationErrors(cve);
+ }
+}
diff --git a/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/package-info.java b/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/package-info.java
new file mode 100644
index 0000000..8288c8e
--- /dev/null
+++ b/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/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 server-side classes adding support of bean validation to MVC (Model View Controller).
+ */
+package org.glassfish.jersey.server.mvc.beanvalidation;
diff --git a/ext/mvc-freemarker/pom.xml b/ext/mvc-freemarker/pom.xml
new file mode 100644
index 0000000..73b152d
--- /dev/null
+++ b/ext/mvc-freemarker/pom.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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.ext</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-mvc-freemarker</artifactId>
+ <name>jersey-ext-mvc-freemarker</name>
+
+ <description>
+ Jersey extension module providing support for Freemarker templates.
+ </description>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>org.glassfish.jersey.server.mvc.freemarker.*;version=${project.version}</Export-Package>
+ </instructions>
+ <unpackBundle>true</unpackBundle>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-mvc</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.freemarker</groupId>
+ <artifactId>freemarker</artifactId>
+ <version>${freemarker.version}</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerConfigurationFactory.java b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerConfigurationFactory.java
new file mode 100644
index 0000000..d62bf18
--- /dev/null
+++ b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerConfigurationFactory.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.mvc.freemarker;
+
+import freemarker.template.Configuration;
+
+
+/**
+ * Provides lookup of {@link freemarker.template.Configuration Configuration}
+ * instance for Freemarker templating.
+ * </p>
+ * Instantiation of Configuration objects is relatively heavy-weight, and
+ * Freemarker best-practices dictate that they be reused if possible.
+ * Therefore, most implementations of this interface will only create a
+ * singleton Configuration instance, and return it for every call to
+ * {@link #getConfiguration()}. Although this will usually be the case, it is
+ * not a guarantee of this interface's contract.
+ *
+ * @author Jeff Wilde (jeff.wilde at complicatedrobot.com)
+ */
+public interface FreemarkerConfigurationFactory {
+
+ public Configuration getConfiguration();
+
+}
diff --git a/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerDefaultConfigurationFactory.java b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerDefaultConfigurationFactory.java
new file mode 100644
index 0000000..7ec7fed
--- /dev/null
+++ b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerDefaultConfigurationFactory.java
@@ -0,0 +1,75 @@
+/*
+ * 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.server.mvc.freemarker;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.ServletContext;
+
+import freemarker.cache.ClassTemplateLoader;
+import freemarker.cache.FileTemplateLoader;
+import freemarker.cache.MultiTemplateLoader;
+import freemarker.cache.TemplateLoader;
+import freemarker.cache.WebappTemplateLoader;
+import freemarker.template.Configuration;
+
+/**
+ * Handy {@link FreemarkerConfigurationFactory} that supplies a minimally
+ * configured {@link freemarker.template.Configuration Configuration} able to
+ * create {@link freemarker.template.Template Freemarker templates}.
+ * The recommended method to provide custom Freemarker configuration is to
+ * sub-class this class, further customize the
+ * {@link freemarker.template.Configuration configuration} as desired in that
+ * class, and then register the sub-class with the {@link FreemarkerMvcFeature}
+ * TEMPLATE_OBJECT_FACTORY property.
+ *
+ * @author Jeff Wilde (jeff.wilde at complicatedrobot.com)
+ */
+public class FreemarkerDefaultConfigurationFactory implements FreemarkerConfigurationFactory {
+
+ protected final Configuration configuration;
+
+ public FreemarkerDefaultConfigurationFactory(ServletContext servletContext) {
+ super();
+
+ // Create different loaders.
+ final List<TemplateLoader> loaders = new ArrayList<>();
+ if (servletContext != null) {
+ loaders.add(new WebappTemplateLoader(servletContext));
+ }
+ loaders.add(new ClassTemplateLoader(FreemarkerDefaultConfigurationFactory.class, "/"));
+ try {
+ loaders.add(new FileTemplateLoader(new File("/")));
+ } catch (IOException e) {
+ // NOOP
+ }
+
+ // Create Base configuration.
+ configuration = new Configuration();
+ configuration.setTemplateLoader(new MultiTemplateLoader(loaders.toArray(new TemplateLoader[loaders.size()])));
+
+ }
+
+ @Override
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+}
diff --git a/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerMvcFeature.java b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerMvcFeature.java
new file mode 100644
index 0000000..2dab451
--- /dev/null
+++ b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerMvcFeature.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2013, 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.mvc.freemarker;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.server.mvc.MvcFeature;
+
+/**
+ * {@link Feature} used to add support for {@link MvcFeature MVC} and Freemarker templates.
+ * <p/>
+ * Note: This feature also registers {@link MvcFeature}.
+ *
+ * @author Michal Gajdos
+ * @author Jeff Wilde (jeff.wilde at complicatedrobot.com)
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public final class FreemarkerMvcFeature implements Feature {
+
+ private static final String SUFFIX = ".freemarker";
+
+ /**
+ * {@link String} property defining the base path to Freemarker templates. If set, the value of the property is added in front
+ * of the template name defined in:
+ * <ul>
+ * <li>{@link org.glassfish.jersey.server.mvc.Viewable Viewable}</li>
+ * <li>{@link org.glassfish.jersey.server.mvc.Template Template}, or</li>
+ * <li>{@link org.glassfish.jersey.server.mvc.ErrorTemplate ErrorTemplate}</li>
+ * </ul>
+ * <p/>
+ * Value can be absolute providing a full path to a system directory with templates or relative to current
+ * {@link javax.servlet.ServletContext servlet context}.
+ * <p/>
+ * There is no default value.
+ * <p/>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ */
+ public static final String TEMPLATE_BASE_PATH = MvcFeature.TEMPLATE_BASE_PATH + SUFFIX;
+
+ /**
+ * If {@code true} then enable caching of Freemarker templates to avoid multiple compilation.
+ * <p/>
+ * The default value is {@code false}.
+ * <p/>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ *
+ * @since 2.5
+ */
+ public static final String CACHE_TEMPLATES = MvcFeature.CACHE_TEMPLATES + SUFFIX;
+
+ /**
+ * Property used to pass user-configured {@link org.glassfish.jersey.server.mvc.freemarker.FreemarkerConfigurationFactory}.
+ * <p/>
+ * The default value is not set.
+ * <p/>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ * <p/>
+ * This property will also accept an instance of {@link freemarker.template.Configuration Configuration} directly, to
+ * support backwards compatibility. If you want to set custom {@link freemarker.template.Configuration configuration} then set
+ * {@link freemarker.cache.TemplateLoader template loader} to multi loader of:
+ * {@link freemarker.cache.WebappTemplateLoader} (if applicable), {@link freemarker.cache.ClassTemplateLoader} and
+ * {@link freemarker.cache.FileTemplateLoader} keep functionality of resolving templates.
+ * <p/>
+ * If no value is set, a {@link org.glassfish.jersey.server.mvc.freemarker.FreemarkerDefaultConfigurationFactory factory}
+ * with the above behaviour is used by default in the
+ * {@link org.glassfish.jersey.server.mvc.freemarker.FreemarkerViewProcessor} class.
+ * <p/>
+ *
+ * @since 2.5
+ */
+ public static final String TEMPLATE_OBJECT_FACTORY = MvcFeature.TEMPLATE_OBJECT_FACTORY + SUFFIX;
+
+ /**
+ * Property defines output encoding produced by {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor}.
+ * The value must be a valid encoding defined that can be passed
+ * to the {@link java.nio.charset.Charset#forName(String)} method.
+ *
+ * <p/>
+ * The default value is {@code UTF-8}.
+ * <p/>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ * <p/>
+ *
+ * @since 2.7
+ */
+ public static final String ENCODING = MvcFeature.ENCODING + SUFFIX;
+
+ @Override
+ public boolean configure(final FeatureContext context) {
+ final Configuration config = context.getConfiguration();
+
+ if (!config.isRegistered(FreemarkerViewProcessor.class)) {
+ // Template Processor.
+ context.register(FreemarkerViewProcessor.class);
+
+ // MvcFeature.
+ if (!config.isRegistered(MvcFeature.class)) {
+ context.register(MvcFeature.class);
+ }
+
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerSuppliedConfigurationFactory.java b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerSuppliedConfigurationFactory.java
new file mode 100644
index 0000000..35a2dcb
--- /dev/null
+++ b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerSuppliedConfigurationFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.mvc.freemarker;
+
+
+import freemarker.template.Configuration;
+
+/**
+ * {@link FreemarkerConfigurationFactory} that supplies an unchanged
+ * {@link freemarker.template.Configuration Configuration} as passed-in to
+ * the constructor.
+ * <p/>
+ * Used to support backwards-compatibility in {@link FreemarkerViewProcessor}
+ * to wrap directly-configured {@link freemarker.template.Configuration Configuration}
+ * objects instead of the recommended {@link FreemarkerDefaultConfigurationFactory}
+ * or a sub-class thereof.
+ *
+ * @author Jeff Wilde (jeff.wilde at complicatedrobot.com)
+ */
+final class FreemarkerSuppliedConfigurationFactory implements FreemarkerConfigurationFactory {
+
+ private final Configuration configuration;
+
+ public FreemarkerSuppliedConfigurationFactory(Configuration configuration) {
+ this.configuration = configuration;
+ }
+
+ @Override
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+}
diff --git a/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerViewProcessor.java b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerViewProcessor.java
new file mode 100644
index 0000000..164f4ab
--- /dev/null
+++ b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerViewProcessor.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2013, 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.mvc.freemarker;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import javax.inject.Inject;
+import javax.servlet.ServletContext;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.util.collection.Values;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.server.mvc.spi.AbstractTemplateProcessor;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+
+/**
+ * {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor Template processor} providing support for Freemarker templates.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Michal Gajdos
+ * @author Jeff Wilde (jeff.wilde at complicatedrobot.com)
+ */
+final class FreemarkerViewProcessor extends AbstractTemplateProcessor<Template> {
+
+ private final FreemarkerConfigurationFactory factory;
+
+ /**
+ * Create an instance of this processor with injected {@link javax.ws.rs.core.Configuration config} and
+ * (optional) {@link javax.servlet.ServletContext servlet context}.
+ *
+ * @param config config to configure this processor from.
+ * @param injectionManager injection manager.
+ */
+ @Inject
+ public FreemarkerViewProcessor(javax.ws.rs.core.Configuration config, InjectionManager injectionManager) {
+ super(config, injectionManager.getInstance(ServletContext.class), "freemarker", "ftl");
+
+ this.factory = getTemplateObjectFactory(injectionManager::createAndInitialize, FreemarkerConfigurationFactory.class,
+ () -> {
+ Configuration configuration =
+ getTemplateObjectFactory(injectionManager::createAndInitialize, Configuration.class, Values.empty());
+ if (configuration == null) {
+ return new FreemarkerDefaultConfigurationFactory(injectionManager.getInstance(ServletContext.class));
+ } else {
+ return new FreemarkerSuppliedConfigurationFactory(configuration);
+ }
+ });
+ }
+
+ @Override
+ protected Template resolve(final String templateReference, final Reader reader) throws Exception {
+ return factory.getConfiguration().getTemplate(templateReference);
+ }
+
+ @Override
+ public void writeTo(final Template template, final Viewable viewable, final MediaType mediaType,
+ final MultivaluedMap<String, Object> httpHeaders, final OutputStream out) throws IOException {
+ try {
+ Object model = viewable.getModel();
+ if (!(model instanceof Map)) {
+ model = new HashMap<String, Object>() {{
+ put("model", viewable.getModel());
+ }};
+ }
+ Charset encoding = setContentType(mediaType, httpHeaders);
+
+ template.process(model, new OutputStreamWriter(out, encoding));
+ } catch (TemplateException te) {
+ throw new ContainerException(te);
+ }
+ }
+}
diff --git a/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/package-info.java b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/package-info.java
new file mode 100644
index 0000000..26762f3
--- /dev/null
+++ b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/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 server-side classes adding support of FreeMarker to Jersey MVC (Model View Controller).
+ */
+package org.glassfish.jersey.server.mvc.freemarker;
diff --git a/ext/mvc-jsp/pom.xml b/ext/mvc-jsp/pom.xml
new file mode 100644
index 0000000..e76f2f5
--- /dev/null
+++ b/ext/mvc-jsp/pom.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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.ext</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-mvc-jsp</artifactId>
+ <name>jersey-ext-mvc-jsp</name>
+
+ <description>
+ Jersey extension module providing support for JSP templates.
+ </description>
+
+ <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>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ javax.servlet.jsp.*;version="${range;[==,+);${jsp.version}}",
+ *
+ </Import-Package>
+ <Export-Package>org.glassfish.jersey.server.mvc.jsp.*;version=${project.version}</Export-Package>
+ </instructions>
+ <unpackBundle>true</unpackBundle>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.hk2.external</groupId>
+ <artifactId>javax.inject</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-mvc</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>${servlet2.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet.jsp</groupId>
+ <artifactId>jsp-api</artifactId>
+ <version>${jsp.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/Include.java b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/Include.java
new file mode 100644
index 0000000..90b6e1b
--- /dev/null
+++ b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/Include.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2010, 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.mvc.jsp;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.SimpleTagSupport;
+
+import org.glassfish.jersey.server.mvc.internal.TemplateHelper;
+import org.glassfish.jersey.server.mvc.jsp.internal.LocalizationMessages;
+
+/**
+ * Includes a side JSP file for the {@code resolvingClass} class.
+ * <p/>
+ * This tag looks for a side JSP file of the given name
+ * from the inheritance hierarchy of the "resolvingClass" class,
+ * and includes the contents of it, just like <jsp:include>.
+ * <p/>
+ * For example, if the {@code resolvingClass} class is the {@code Foo} class,
+ * which looks like the following:
+ * <pre>
+ * class Foo extends Bar { ... }
+ * class Bar extends Zot { ... }
+ * </pre>
+ * <p/>
+ * And if you write:
+ * <pre>
+ * <st:include page="abc.jsp"/>
+ * </pre>
+ * then, it looks for the following files in this order,
+ * and includes the first one found.
+ * <ol>
+ * <li>a side-file of the {@code Foo} class named {@code abc.jsp} ({@code /WEB-INF/Foo/abc.jsp})
+ * <li>a side-file of the {@code Bar} class named {@code abc.jsp} ({@code /WEB-INF/Bar/abc.jsp})
+ * <li>a side-file of the {@code Zot} class named {@code abc.jsp} ({@code /WEB-INF/Zot/abc.jsp})
+ * </ol>
+ *
+ * @author Kohsuke Kawaguchi
+ * @author Paul Sandoz
+ */
+public class Include extends SimpleTagSupport {
+
+ private String page;
+
+ /**
+ * Specifies the name of the JSP to be included.
+ *
+ * @param page page to be included.
+ */
+ public void setPage(String page) {
+ this.page = page;
+ }
+
+ private Object getPageObject(String name) {
+ return getJspContext().getAttribute(name, PageContext.PAGE_SCOPE);
+ }
+
+ public void doTag() throws JspException, IOException {
+ final JspContext jspContext = getJspContext();
+ final Class<?> resolvingClass = (Class<?>) jspContext
+ .getAttribute(RequestDispatcherWrapper.RESOLVING_CLASS_ATTRIBUTE_NAME, PageContext.REQUEST_SCOPE);
+ final String basePath = (String) jspContext.getAttribute(RequestDispatcherWrapper.BASE_PATH_ATTRIBUTE_NAME,
+ PageContext.REQUEST_SCOPE);
+
+ final ServletConfig servletConfig = (ServletConfig) getPageObject(PageContext.CONFIG);
+ final ServletContext servletContext = servletConfig.getServletContext();
+
+ for (Class<?> clazz = resolvingClass; clazz != Object.class; clazz = clazz.getSuperclass()) {
+ final String template = basePath + TemplateHelper.getAbsolutePath(clazz, page, '/');
+
+ if (servletContext.getResource(template) != null) {
+ // Tomcat returns a RequestDispatcher even if the JSP file doesn't exist so check if the resource exists first.
+ final RequestDispatcher dispatcher = servletContext.getRequestDispatcher(template);
+
+ if (dispatcher != null) {
+ try {
+ final HttpServletRequest request = (HttpServletRequest) getPageObject(PageContext.REQUEST);
+ final HttpServletResponse response = (HttpServletResponse) getPageObject(PageContext.RESPONSE);
+
+ dispatcher.include(request,
+ new Wrapper(response, new PrintWriter(jspContext.getOut())));
+ } catch (ServletException e) {
+ throw new JspException(e);
+ }
+ return;
+ }
+ }
+ }
+
+ throw new JspException(LocalizationMessages.UNABLE_TO_FIND_PAGE_FOR_RESOLVING_CLASS(page, resolvingClass));
+ }
+
+ class Wrapper extends HttpServletResponseWrapper {
+
+ private final PrintWriter writer;
+
+ Wrapper(HttpServletResponse httpServletResponse, PrintWriter w) {
+ super(httpServletResponse);
+ this.writer = w;
+ }
+
+ public PrintWriter getWriter() throws IOException {
+ return writer;
+ }
+ }
+}
diff --git a/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/JspMvcFeature.java b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/JspMvcFeature.java
new file mode 100644
index 0000000..3b70480
--- /dev/null
+++ b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/JspMvcFeature.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2013, 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.mvc.jsp;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.server.mvc.MvcFeature;
+
+/**
+ * {@link Feature} used to add support for {@link MvcFeature MVC} and JSP templates.
+ * <p/>
+ * Note: This feature also registers {@link MvcFeature}.
+ *
+ * @author Michal Gajdos
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public final class JspMvcFeature implements Feature {
+
+ private static final String SUFFIX = ".jsp";
+
+ /**
+ * {@link String} property defining the base path to JSP templates. If set, the value of the property is added in front
+ * of the template name defined in:
+ * <ul>
+ * <li>{@link org.glassfish.jersey.server.mvc.Viewable Viewable}</li>
+ * <li>{@link org.glassfish.jersey.server.mvc.Template Template}, or</li>
+ * <li>{@link org.glassfish.jersey.server.mvc.ErrorTemplate ErrorTemplate}</li>
+ * </ul>
+ * <p/>
+ * Value can be absolute or relative to current {@link javax.servlet.ServletContext servlet context}.
+ * <p/>
+ * There is no default value.
+ * <p/>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ */
+ public static final String TEMPLATE_BASE_PATH = MvcFeature.TEMPLATE_BASE_PATH + SUFFIX;
+
+ @Override
+ public boolean configure(final FeatureContext context) {
+ final Configuration config = context.getConfiguration();
+
+ if (!config.isRegistered(JspTemplateProcessor.class)) {
+ // Template Processor.
+ context.register(JspTemplateProcessor.class);
+
+ // MvcFeature.
+ if (!config.isRegistered(MvcFeature.class)) {
+ context.register(MvcFeature.class);
+ }
+
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/JspTemplateProcessor.java b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/JspTemplateProcessor.java
new file mode 100644
index 0000000..755762f
--- /dev/null
+++ b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/JspTemplateProcessor.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2013, 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.mvc.jsp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Reader;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.message.internal.TracingLogger;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.server.mvc.jsp.internal.LocalizationMessages;
+import org.glassfish.jersey.server.mvc.spi.AbstractTemplateProcessor;
+import org.glassfish.jersey.server.mvc.spi.ResolvedViewable;
+
+/**
+ * A JSP template processor able to process resources obtained through {@link ServletContext servlet context}. This template
+ * processor does not support caching of template reference objects (in Jersey) or passing custom template object factory.
+ *
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+final class JspTemplateProcessor extends AbstractTemplateProcessor<String> {
+
+ @Inject
+ private Provider<Ref<HttpServletRequest>> requestProviderRef;
+ @Inject
+ private Provider<Ref<HttpServletResponse>> responseProviderRef;
+ @Inject
+ private Provider<ContainerRequest> containerRequestProvider;
+
+ /**
+ * Create an instance of this processor with injected {@link Configuration config} and
+ * (optional) {@link ServletContext servlet context}.
+ *
+ * @param config configuration to configure this processor from.
+ * @param servletContext (optional) servlet context to obtain template resources from.
+ */
+ @Inject
+ public JspTemplateProcessor(final Configuration config, final ServletContext servletContext) {
+ super(config, servletContext, "jsp", "jsp");
+ }
+
+ @Override
+ protected String resolve(final String templatePath, final Reader reader) throws Exception {
+ return templatePath;
+ }
+
+ @Override
+ public void writeTo(final String templateReference, final Viewable viewable, final MediaType mediaType,
+ final MultivaluedMap<String, Object> httpHeaders, final OutputStream out) throws IOException {
+
+ if (!(viewable instanceof ResolvedViewable)) {
+ // This should not happen with default MVC message body writer implementation
+ throw new IllegalArgumentException(LocalizationMessages.ERROR_VIEWABLE_INCORRECT_INSTANCE());
+ }
+
+ // SPI could supply instance of ResolvedViewable but we would like to keep the backward
+ // compatibility, so the cast is here.
+ final ResolvedViewable resolvedViewable = (ResolvedViewable) viewable;
+
+
+ final TracingLogger tracingLogger = TracingLogger.getInstance(containerRequestProvider.get().getPropertiesDelegate());
+ if (tracingLogger.isLogEnabled(MvcJspEvent.JSP_FORWARD)) {
+ tracingLogger.log(MvcJspEvent.JSP_FORWARD, templateReference, resolvedViewable.getModel());
+ }
+
+ final RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(templateReference);
+ if (dispatcher == null) {
+ throw new ContainerException(LocalizationMessages.NO_REQUEST_DISPATCHER_FOR_RESOLVED_PATH(templateReference));
+ }
+
+ final RequestDispatcher wrapper = new RequestDispatcherWrapper(dispatcher, getBasePath(), resolvedViewable);
+
+ // OutputStream and Writer for HttpServletResponseWrapper.
+ final ServletOutputStream responseStream = new ServletOutputStream() {
+ @Override
+ public void write(final int b) throws IOException {
+ out.write(b);
+ }
+ };
+ final PrintWriter responseWriter = new PrintWriter(new OutputStreamWriter(responseStream, getEncoding()));
+
+ try {
+ wrapper.forward(requestProviderRef.get().get(), new HttpServletResponseWrapper(responseProviderRef.get().get()) {
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException {
+ return responseStream;
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException {
+ return responseWriter;
+ }
+ });
+ } catch (final Exception e) {
+ throw new ContainerException(e);
+ } finally {
+ responseWriter.flush();
+ }
+ }
+
+ /**
+ * MVC-JSP side tracing events.
+ */
+ private static enum MvcJspEvent implements TracingLogger.Event {
+ JSP_FORWARD(TracingLogger.Level.SUMMARY, "MVC", "Forwarding view to JSP page [%s], model %s");
+
+ private final TracingLogger.Level level;
+ private final String category;
+ private final String messageFormat;
+
+ private MvcJspEvent(final TracingLogger.Level level, final String category, final String messageFormat) {
+ this.level = level;
+ this.category = category;
+ this.messageFormat = messageFormat;
+ }
+
+ @Override
+ public String category() {
+ return category;
+ }
+
+ @Override
+ public TracingLogger.Level level() {
+ return level;
+ }
+
+ @Override
+ public String messageFormat() {
+ return messageFormat;
+ }
+ }
+
+}
diff --git a/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/RequestDispatcherWrapper.java b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/RequestDispatcherWrapper.java
new file mode 100644
index 0000000..761ba90
--- /dev/null
+++ b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/RequestDispatcherWrapper.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2010, 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.mvc.jsp;
+
+import java.io.IOException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.glassfish.jersey.server.mvc.spi.ResolvedViewable;
+
+/**
+ * {@link RequestDispatcher Request dispatcher wrapper} for setting attributes (e.g. {@code it}).
+ *
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+final class RequestDispatcherWrapper implements RequestDispatcher {
+
+ static final String BASE_PATH_ATTRIBUTE_NAME = "_basePath";
+ static final String OLD_MODEL_ATTRIBUTE_NAME = "it";
+ static final String MODEL_ATTRIBUTE_NAME = "model";
+ static final String RESOLVING_CLASS_ATTRIBUTE_NAME = "resolvingClass";
+ static final String REQUEST_ATTRIBUTE_NAME = "_request";
+ static final String RESPONSE_ATTRIBUTE_NAME = "_response";
+
+ private final RequestDispatcher dispatcher;
+
+ private final String basePath;
+
+ private final ResolvedViewable viewable;
+
+ /**
+ * Creates new {@code RequestDispatcherWrapper} responsible for setting request attributes and forwarding the processing to
+ * the given dispatcher.
+ *
+ * @param dispatcher dispatcher processing the request after all the request attributes were set.
+ * @param basePath base path of all JSP set to {@value #BASE_PATH_ATTRIBUTE_NAME} request attribute.
+ * @param viewable viewable to obtain model and resolving class from.
+ */
+ public RequestDispatcherWrapper(
+ final RequestDispatcher dispatcher, final String basePath, final ResolvedViewable viewable) {
+ this.dispatcher = dispatcher;
+ this.basePath = basePath;
+ this.viewable = viewable;
+ }
+
+ @Override
+ public void forward(final ServletRequest request, final ServletResponse response) throws ServletException, IOException {
+ final Object oldIt = request.getAttribute(MODEL_ATTRIBUTE_NAME);
+ final Object oldResolvingClass = request.getAttribute(RESOLVING_CLASS_ATTRIBUTE_NAME);
+
+ request.setAttribute(RESOLVING_CLASS_ATTRIBUTE_NAME, viewable.getResolvingClass());
+
+ request.setAttribute(OLD_MODEL_ATTRIBUTE_NAME, viewable.getModel());
+ request.setAttribute(MODEL_ATTRIBUTE_NAME, viewable.getModel());
+
+ request.setAttribute(BASE_PATH_ATTRIBUTE_NAME, basePath);
+ request.setAttribute(REQUEST_ATTRIBUTE_NAME, request);
+ request.setAttribute(RESPONSE_ATTRIBUTE_NAME, response);
+
+ dispatcher.forward(request, response);
+
+ request.setAttribute(RESOLVING_CLASS_ATTRIBUTE_NAME, oldResolvingClass);
+ request.setAttribute(MODEL_ATTRIBUTE_NAME, oldIt);
+ }
+
+ @Override
+ public void include(final ServletRequest request, final ServletResponse response) throws ServletException, IOException {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/package-info.java b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/package-info.java
new file mode 100644
index 0000000..23c4326
--- /dev/null
+++ b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2013, 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 server-side MVC support for servlet containers.
+ * <p/>
+ * Note: Jersey applications that want to use MVC features should be registered as filters instead of servlets in web.xml (to
+ * fully take advantage of {@link org.glassfish.jersey.server.mvc.jsp.JspMvcFeature}). Web.xml-less deployment of an application
+ * using MVC is not supported at the moment.
+ *
+ * @see org.glassfish.jersey.server.mvc.jsp.JspTemplateProcessor
+ */
+package org.glassfish.jersey.server.mvc.jsp;
diff --git a/ext/mvc-jsp/src/main/resources/META-INF/taglib.tld b/ext/mvc-jsp/src/main/resources/META-INF/taglib.tld
new file mode 100644
index 0000000..95e6585
--- /dev/null
+++ b/ext/mvc-jsp/src/main/resources/META-INF/taglib.tld
@@ -0,0 +1,61 @@
+<?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
+
+-->
+
+<taglib xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
+ version="2.1">
+
+ <description>The tag library contains tags that takes advantage of the request dispatching mechanism.</description>
+ <tlib-version>1.0</tlib-version>
+ <short-name>jersey-mvc-jsp</short-name>
+ <uri>urn:org:glassfish:jersey:servlet:mvc</uri>
+
+ <tag>
+ <name>include</name>
+ <tag-class>org.glassfish.jersey.server.mvc.jsp.Include</tag-class>
+ <body-content>empty</body-content>
+ <attribute>
+ <name>page</name>
+ <required>yes</required>
+ <rtexprvalue>yes</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>resource</name>
+ <required>no</required>
+ <rtexprvalue>yes</rtexprvalue>
+ </attribute>
+ </tag>
+</taglib>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ext/mvc-jsp/src/main/resources/org.glassfish.jersey.server.mvc.jsp.internal/localization.properties b/ext/mvc-jsp/src/main/resources/org.glassfish.jersey.server.mvc.jsp.internal/localization.properties
new file mode 100644
index 0000000..b81cd4c
--- /dev/null
+++ b/ext/mvc-jsp/src/main/resources/org.glassfish.jersey.server.mvc.jsp.internal/localization.properties
@@ -0,0 +1,22 @@
+#
+# Copyright (c) 2013, 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
+#
+
+# {0} - path
+error.viewable.incorrect.instance=viewable argument must be an instance of ResolvedViewable.
+no.request.dispatcher.for.resolved.path=No request dispatcher for: {0}
+# {0} - path
+resource.path.not.in.correct.form=Resource path "{0}" is not in correct form.
+unable.to.find.page.for.resolving.class=Unable to find "{0}" for {1}.
diff --git a/ext/mvc-mustache/pom.xml b/ext/mvc-mustache/pom.xml
new file mode 100644
index 0000000..b3c747c
--- /dev/null
+++ b/ext/mvc-mustache/pom.xml
@@ -0,0 +1,73 @@
+<?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.ext</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-mvc-mustache</artifactId>
+ <name>jersey-ext-mvc-mustache</name>
+
+ <description>
+ Jersey extension module providing support for Mustache templates.
+ </description>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>org.glassfish.jersey.server.mvc.mustache.*;version=${project.version}</Export-Package>
+ </instructions>
+ <unpackBundle>true</unpackBundle>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <artifactId>jersey-mvc</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.github.spullara.mustache.java</groupId>
+ <artifactId>compiler</artifactId>
+ <version>${mustache.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>${servlet2.version}</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/MustacheMvcFeature.java b/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/MustacheMvcFeature.java
new file mode 100644
index 0000000..9677253
--- /dev/null
+++ b/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/MustacheMvcFeature.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2013, 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.mvc.mustache;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.server.mvc.MvcFeature;
+
+/**
+ * {@link Feature} used to add support for {@link MvcFeature MVC} and Mustache templates.
+ * <p/>
+ * Note: This feature also registers {@link MvcFeature}.
+ *
+ * @author Michal Gajdos
+ * @since 2.3
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public class MustacheMvcFeature implements Feature {
+
+ private static final String SUFFIX = ".mustache";
+
+ /**
+ * {@link String} property defining the base path to Mustache templates. If set, the value of the property is added in front
+ * of the template name defined in:
+ * <ul>
+ * <li>{@link org.glassfish.jersey.server.mvc.Viewable Viewable}</li>
+ * <li>{@link org.glassfish.jersey.server.mvc.Template Template}, or</li>
+ * <li>{@link org.glassfish.jersey.server.mvc.ErrorTemplate ErrorTemplate}</li>
+ * </ul>
+ * <p/>
+ * Value can be absolute providing a full path to a system directory with templates or relative to current
+ * {@link javax.servlet.ServletContext servlet context}.
+ * <p/>
+ * There is no default value.
+ * <p/>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ */
+ public static final String TEMPLATE_BASE_PATH = MvcFeature.TEMPLATE_BASE_PATH + SUFFIX;
+
+ /**
+ * If {@code true} then enable caching of Mustache templates to avoid multiple compilation.
+ * <p/>
+ * The default value is {@code false}.
+ * <p/>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ *
+ * @since 2.5
+ */
+ public static final String CACHE_TEMPLATES = MvcFeature.CACHE_TEMPLATES + SUFFIX;
+
+ /**
+ * Property used to pass user-configured {@link com.github.mustachejava.MustacheFactory factory} able to create
+ * {@link com.github.mustachejava.Mustache Mustache templates}.
+ * <p/>
+ * The default value is not set.
+ * <p/>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ *
+ * @since 2.5
+ */
+ public static final String TEMPLATE_OBJECT_FACTORY = MvcFeature.TEMPLATE_OBJECT_FACTORY + SUFFIX;
+
+ /**
+ * Property defines output encoding produced by {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor}.
+ * The value must be a valid encoding defined that can be passed
+ * to the {@link java.nio.charset.Charset#forName(String)} method.
+ *
+ * <p/>
+ * The default value is {@code UTF-8}.
+ * <p/>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ * <p/>
+ *
+ * @since 2.7
+ */
+ public static final String ENCODING = MvcFeature.ENCODING + SUFFIX;
+
+ @Override
+ public boolean configure(final FeatureContext context) {
+ final Configuration config = context.getConfiguration();
+
+ if (!config.isRegistered(MustacheTemplateProcessor.class)) {
+ // Template Processor.
+ context.register(MustacheTemplateProcessor.class);
+
+ // MvcFeature.
+ if (!config.isRegistered(MvcFeature.class)) {
+ context.register(MvcFeature.class);
+ }
+
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/MustacheTemplateProcessor.java b/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/MustacheTemplateProcessor.java
new file mode 100644
index 0000000..a39c50b
--- /dev/null
+++ b/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/MustacheTemplateProcessor.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2013, 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.mvc.mustache;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.nio.charset.Charset;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.ServletContext;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.server.mvc.spi.AbstractTemplateProcessor;
+import org.glassfish.jersey.server.mvc.spi.TemplateProcessor;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+
+/**
+ * {@link TemplateProcessor Template processor} providing support for Mustache templates.
+ *
+ * @author Michal Gajdos
+ * @see MustacheMvcFeature
+ * @since 2.3
+ */
+@Singleton
+final class MustacheTemplateProcessor extends AbstractTemplateProcessor<Mustache> {
+
+ private final MustacheFactory factory;
+
+ /**
+ * Create an instance of this processor with injected {@link Configuration config} and (nullable)
+ * {@link ServletContext servlet context}.
+ *
+ * @param config configuration to configure this processor from.
+ * @param injectionManager injection manager.
+ */
+ @Inject
+ public MustacheTemplateProcessor(Configuration config, InjectionManager injectionManager) {
+ super(config, injectionManager.getInstance(ServletContext.class), "mustache", "mustache");
+
+ this.factory = getTemplateObjectFactory(injectionManager::createAndInitialize, MustacheFactory.class,
+ DefaultMustacheFactory::new);
+ }
+
+ @Override
+ protected Mustache resolve(final String templatePath, final Reader reader) {
+ return factory.compile(reader, templatePath);
+ }
+
+ @Override
+ public void writeTo(final Mustache mustache, final Viewable viewable, final MediaType mediaType,
+ final MultivaluedMap<String, Object> httpHeaders, final OutputStream out) throws IOException {
+ Charset encoding = setContentType(mediaType, httpHeaders);
+ mustache.execute(new OutputStreamWriter(out, encoding), viewable.getModel()).flush();
+ }
+}
diff --git a/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/package-info.java b/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/package-info.java
new file mode 100644
index 0000000..297ab09
--- /dev/null
+++ b/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/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 server-side classes adding support of Mustache to Jersey MVC (Model View Controller).
+ */
+package org.glassfish.jersey.server.mvc.mustache;
diff --git a/ext/mvc/pom.xml b/ext/mvc/pom.xml
new file mode 100644
index 0000000..9a0ff5e
--- /dev/null
+++ b/ext/mvc/pom.xml
@@ -0,0 +1,76 @@
+<?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.ext</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-mvc</artifactId>
+ <name>jersey-ext-mvc</name>
+
+ <description>
+ Jersey extension module providing support for MVC.
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>${servlet2.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ </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>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>org.glassfish.jersey.server.mvc.*;version=${project.version}</Export-Package>
+ </instructions>
+ <unpackBundle>true</unpackBundle>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/ErrorTemplate.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/ErrorTemplate.java
new file mode 100644
index 0000000..4400c93
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/ErrorTemplate.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.mvc;
+
+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;
+
+/**
+ * Used to annotate JAX-RS resources and resource methods to provide reference to an error template if an exception has been
+ * raised during processing a request (resource method invocation).
+ * <p/>
+ * The processing of this annotation is similar to the processing of {@link Template} annotation with the difference that the
+ * thrown exception (or an object derived from the exception) is used as a model for the defined template.
+ * <p/>
+ * By default every {@link Exception exception} is mapped to a viewable and passed to the MVC runtime for further processing.
+ * <p/>
+ * Note: The {@link ErrorTemplate} annotation can be used even in case when neither {@link Viewable viewable} is used as return
+ * value of a resource method nor {@link Template} annotation is used to annotate the resource method or resource class.
+ *
+ * @author Michal Gajdos
+ * @see Template
+ * @see org.glassfish.jersey.server.mvc.spi.AbstractErrorTemplateMapper
+ * @since 2.3
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Documented
+public @interface ErrorTemplate {
+
+ /**
+ * The template name that should be used to display an error raised during processing a request. The template name may be
+ * declared as absolute template name if the name begins with a '/', otherwise the template name is recognized to be relative.
+ */
+ String name() default "";
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/MvcFeature.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/MvcFeature.java
new file mode 100644
index 0000000..d83376c
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/MvcFeature.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.mvc;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.server.mvc.internal.ErrorTemplateExceptionMapper;
+import org.glassfish.jersey.server.mvc.internal.MvcBinder;
+
+/**
+ * {@code MvcFeature} used to add MVC support to the server.
+ *
+ * @author Michal Gajdos
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public final class MvcFeature implements Feature {
+
+ /**
+ * {@link String} property defining the base path to MVC templates. If set, the value of the property is added in front
+ * of the template name defined in:
+ * <ul>
+ * <li>{@link org.glassfish.jersey.server.mvc.Viewable Viewable}</li>
+ * <li>{@link org.glassfish.jersey.server.mvc.Template Template}, or</li>
+ * <li>{@link org.glassfish.jersey.server.mvc.ErrorTemplate ErrorTemplate}</li>
+ * </ul>
+ * <p/>
+ * Value can be absolute providing a full path to a system directory with templates or relative to current
+ * {@link javax.servlet.ServletContext servlet context}.
+ * <p/>
+ * There is no default value.
+ * <p/>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ */
+ public static final String TEMPLATE_BASE_PATH = "jersey.config.server.mvc.templateBasePath";
+
+ /**
+ * If {@code true} then enable caching of template objects, i.e. to avoid multiple compilations of a template.
+ * <p/>
+ * The default value is {@code false}.
+ * <p/>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ * <p/>
+ * Note: This property is used as common prefix for specific
+ * {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor template processors} properties and might not be supported by
+ * all template processors.
+ *
+ * @since 2.5
+ */
+ public static final String CACHE_TEMPLATES = "jersey.config.server.mvc.caching";
+
+ /**
+ * Property used to pass user-configured factory able to create template objects. Value of the property is supposed to be an
+ * instance of "templating engine"-specific factory, a class of the factory or class-name of the factory.
+ * <p/>
+ * The default value is not set.
+ * <p/>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ * <p/>
+ * Note: This property is used as common prefix for specific
+ * {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor template processors} properties and might not be supported by
+ * all template processors.
+ *
+ * @since 2.5
+ */
+ public static final String TEMPLATE_OBJECT_FACTORY = "jersey.config.server.mvc.factory";
+
+ /**
+ * Property defines output encoding produced by {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor}. The value
+ * must be a valid encoding defined that can be passed to the {@link java.nio.charset.Charset#forName(String)} method.
+ * <p/>
+ * The default value is {@code UTF-8}.
+ * <p/>
+ * The name of the configuration property is <tt>{@value}</tt>.
+ * <p/>
+ * Note: This property is used as common prefix for specific
+ * {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor template processors} properties and might not be supported by
+ * all template processors.
+ *
+ * @since 2.7
+ */
+ public static final String ENCODING = "jersey.config.server.mvc.encoding";
+
+ @Override
+ public boolean configure(final FeatureContext context) {
+ final Configuration config = context.getConfiguration();
+
+ if (!config.isRegistered(ErrorTemplateExceptionMapper.class)) {
+ context.register(ErrorTemplateExceptionMapper.class);
+ context.register(new MvcBinder());
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/Template.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/Template.java
new file mode 100644
index 0000000..b0b5bfa
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/Template.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.mvc;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Used to annotate JAX-RS resources and resource methods to provide reference to a template for MVC support.
+ * <p/>
+ * In case a resource class is annotated with {@link Template} annotation then an instance of this class is considered to be
+ * the model. Producible {@link javax.ws.rs.core.MediaType media types} are determined from the resource classes
+ * {@link javax.ws.rs.Produces} annotation.
+ * <p/>
+ * In case a resource method is annotated with {@link Template} annotation then the return value of the method is the model.
+ * Otherwise the processing of such a method is the same as if the return type of the method was {@link Viewable} class.
+ * Producible {@link javax.ws.rs.core.MediaType media types} are determined from the method's {@link javax.ws.rs.Produces}
+ * annotation.
+ * <p/>
+ * To see how templates are being resolved, see {@link Viewable viewable}.
+ *
+ * @author Michal Gajdos
+ * @see Viewable
+ */
+@Inherited
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Template {
+
+ /**
+ * The template name that should be used to output the entity. The template name may be declared as absolute template name
+ * if the name begins with a '/', otherwise the template name is declared as a relative template name.
+ */
+ String name() default "";
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/Viewable.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/Viewable.java
new file mode 100644
index 0000000..56703a4
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/Viewable.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2010, 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.mvc;
+
+import org.glassfish.jersey.server.mvc.internal.LocalizationMessages;
+
+/**
+ * A viewable type referencing a template by name and a model to be passed
+ * to the template. Such a type may be returned by a resource method of a
+ * resource class. In this respect the template is the view and the controller
+ * is the resource class in the Model View Controller pattern.
+ * <p/>
+ * The template name may be declared as absolute template name if the name
+ * begins with a '/', otherwise the template name is declared as a relative
+ * template name. If the template name is relative then the class of the
+ * last matching resource is utilized to create an absolute path by default. However,
+ * the responsibility of resolving the absolute template name is up to
+ * {@link org.glassfish.jersey.server.mvc.spi.ViewableContext} which can override the
+ * default resolving behaviour.
+ * <p/>
+ *
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ *
+ * @see Template
+ * @see org.glassfish.jersey.server.mvc.spi.ViewableContext
+ * @see org.glassfish.jersey.server.mvc.internal.ResolvingViewableContext
+ */
+public class Viewable {
+
+ private final String templateName;
+
+ private final Object model;
+
+ /**
+ * Construct a new viewable type with a template name.
+ * <p/>
+ * The model will be set to {@code null}.
+ *
+ * @param templateName the template name, shall not be {@code null}.
+ * @throws IllegalArgumentException if the template name is {@code null}.
+ */
+ public Viewable(String templateName) throws IllegalArgumentException {
+ this(templateName, null);
+ }
+
+ /**
+ * Construct a new viewable type with a template name and a model.
+ *
+ * @param templateName the template name, shall not be {@code null}.
+ * @param model the model, may be {@code null}.
+ * @throws IllegalArgumentException if the template name is {@code null}.
+ */
+ public Viewable(String templateName, Object model) throws IllegalArgumentException {
+ if (templateName == null) {
+ throw new IllegalArgumentException(LocalizationMessages.TEMPLATE_NAME_MUST_NOT_BE_NULL());
+ }
+
+ this.templateName = templateName;
+ this.model = model;
+ }
+
+ /**
+ * Get the template name.
+ *
+ * @return the template name.
+ */
+ public String getTemplateName() {
+ return templateName;
+ }
+
+ /**
+ * Get the model.
+ *
+ * @return the model.
+ */
+ public Object getModel() {
+ return model;
+ }
+
+
+ /**
+ * Determines whether the template name is represented by an absolute path.
+ *
+ * @return {@code true} if the template name is absolute, and starts with a
+ * '/' character, {@code false} otherwise.
+ */
+ public boolean isTemplateNameAbsolute() {
+ return templateName.length() > 0 && templateName.charAt(0) == '/';
+ }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ErrorTemplateExceptionMapper.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ErrorTemplateExceptionMapper.java
new file mode 100644
index 0000000..8f3c1ff
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ErrorTemplateExceptionMapper.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.mvc.internal;
+
+import org.glassfish.jersey.server.mvc.spi.AbstractErrorTemplateMapper;
+
+/**
+ * Implementation of {@link AbstractErrorTemplateMapper} for every thrown {@link Exception}.
+ *
+ * @author Michal Gajdos
+ * @since 2.3
+ */
+public class ErrorTemplateExceptionMapper extends AbstractErrorTemplateMapper<Exception> {
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ImplicitViewable.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ImplicitViewable.java
new file mode 100644
index 0000000..ebe93ed
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ImplicitViewable.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2013, 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.mvc.internal;
+
+import java.util.List;
+
+import org.glassfish.jersey.server.mvc.Viewable;
+
+/**
+ * {@link Viewable} implementation representing return value of enhancing methods added to
+ * {@link org.glassfish.jersey.server.model.Resource resources} annotated with {@link org.glassfish.jersey.server.mvc.Template}.
+ *
+ * @author Michal Gajdos
+ * @see TemplateModelProcessor
+ * @see org.glassfish.jersey.server.mvc.Template
+ */
+final class ImplicitViewable extends Viewable {
+
+ private final List<String> templateNames;
+
+ private final Class<?> resolvingClass;
+
+ /**
+ * Create a {@code ImplicitViewable}.
+ *
+ * @param templateNames allowed template names for which a {@link Viewable viewable} can be resolved.
+ * @param model the model, may be {@code null}.
+ * @param resolvingClass the class to use to resolve the template name if the template is not absolute,
+ * if {@code null} then the resolving class will be obtained from the last matching resource.
+ * @throws IllegalArgumentException if the template name is {@code null}.
+ */
+ ImplicitViewable(final List<String> templateNames, final Object model, final Class<?> resolvingClass)
+ throws IllegalArgumentException {
+ super("", model);
+
+ this.templateNames = templateNames;
+ this.resolvingClass = resolvingClass;
+ }
+
+ /**
+ * Get allowed template names for which a {@link Viewable viewable} can be resolved.
+ *
+ * @return allowed template names.
+ */
+ public List<String> getTemplateNames() {
+ return templateNames;
+ }
+
+ /**
+ * Get the resolving class.
+ *
+ * @return Resolving class.
+ */
+ public Class<?> getResolvingClass() {
+ return resolvingClass;
+ }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/MvcBinder.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/MvcBinder.java
new file mode 100644
index 0000000..8203178
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/MvcBinder.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.mvc.internal;
+
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.WriterInterceptor;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.server.model.ModelProcessor;
+import org.glassfish.jersey.server.mvc.spi.ViewableContext;
+
+/**
+ * Provides MVC functionality.
+ *
+ * @author Michal Gajdos
+ */
+public class MvcBinder extends AbstractBinder {
+
+ @Override
+ protected void configure() {
+ bind(TemplateMethodInterceptor.class).to(WriterInterceptor.class).in(Singleton.class);
+ //noinspection unchecked
+ bind(ViewableMessageBodyWriter.class).to(MessageBodyWriter.class).in(Singleton.class);
+
+ bind(TemplateModelProcessor.class).to(ModelProcessor.class).in(Singleton.class);
+ bindAsContract(ResolvingViewableContext.class).in(Singleton.class);
+ bind(ResolvingViewableContext.class).to(ViewableContext.class).in(Singleton.class).ranked(Integer.MIN_VALUE);
+ }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ResolvingViewableContext.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ResolvingViewableContext.java
new file mode 100644
index 0000000..bfe4059
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ResolvingViewableContext.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2013, 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.mvc.internal;
+
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.server.mvc.spi.ResolvedViewable;
+import org.glassfish.jersey.server.mvc.spi.TemplateProcessor;
+import org.glassfish.jersey.server.mvc.spi.ViewableContext;
+import org.glassfish.jersey.server.mvc.spi.ViewableContextException;
+
+/**
+ * Default implementation of {@link org.glassfish.jersey.server.mvc.spi.ViewableContext viewable context}.
+ * <p/>
+ * If the template name of given {@link Viewable} is represented as a relative path then the resolving class,
+ * and super classes in the inheritance hierarchy, are utilized to generate the absolute template name as follows.
+ * <br/>
+ * The base path starts with '/' character, followed by the fully qualified class name of the resolving class,
+ * with any '.' and '$' characters replaced with a '/' character, followed by a '/' character,
+ * followed by the relative template name.
+ * <br/>
+ * If the absolute template name cannot be resolved into a template reference (see {@link org.glassfish.jersey.server.mvc.spi
+ * .TemplateProcessor} and {@link org.glassfish.jersey.server.mvc.spi.ViewableContext}) then the super class of the resolving
+ * class is utilized, and is set as the resolving class. Traversal up the inheritance hierarchy proceeds until an absolute
+ * template name can be resolved into a template reference, or the Object class is reached,
+ * which means the absolute template name could not be resolved and an error will result.
+ *
+ * @author Michal Gajdos
+ */
+class ResolvingViewableContext implements ViewableContext {
+
+ /**
+ * Resolve given {@link Viewable viewable} using {@link MediaType media type}, {@code resolving class} and
+ * {@link TemplateProcessor template processor}.
+ *
+ * @param viewable viewable to be resolved.
+ * @param mediaType media type of te output.
+ * @param resourceClass resolving class.
+ * @param templateProcessor template processor to be used.
+ * @return resolved viewable or {@code null} if the viewable cannot be resolved.
+ */
+ public ResolvedViewable resolveViewable(final Viewable viewable, final MediaType mediaType,
+ final Class<?> resourceClass, final TemplateProcessor templateProcessor) {
+ if (viewable.isTemplateNameAbsolute()) {
+ return resolveAbsoluteViewable(viewable, resourceClass, mediaType, templateProcessor);
+ } else {
+ if (resourceClass == null) {
+ throw new ViewableContextException(LocalizationMessages.TEMPLATE_RESOLVING_CLASS_CANNOT_BE_NULL());
+ }
+
+ return resolveRelativeViewable(viewable, resourceClass, mediaType, templateProcessor);
+ }
+ }
+
+ /**
+ * Resolve given {@link Viewable viewable} with absolute template name using {@link MediaType media type} and
+ * {@link TemplateProcessor template processor}.
+ *
+ * @param viewable viewable to be resolved.
+ * @param mediaType media type of te output.
+ * @param resourceClass resource class.
+ * @param templateProcessor template processor to be used.
+ * @return resolved viewable or {@code null} if the viewable cannot be resolved.
+ */
+ @SuppressWarnings("unchecked")
+ private ResolvedViewable resolveAbsoluteViewable(final Viewable viewable, Class<?> resourceClass,
+ final MediaType mediaType,
+ final TemplateProcessor templateProcessor) {
+ final Object resolvedTemplateObject = templateProcessor.resolve(viewable.getTemplateName(), mediaType);
+
+ if (resolvedTemplateObject != null) {
+ return new ResolvedViewable(templateProcessor, resolvedTemplateObject, viewable, resourceClass, mediaType);
+ }
+
+ return null;
+ }
+
+ /**
+ * Resolve given {@link Viewable viewable} with relative template name using {@link MediaType media type},
+ * {@code resolving class} and {@link TemplateProcessor template processor}.
+ *
+ * @param viewable viewable to be resolved.
+ * @param mediaType media type of te output.
+ * @param resolvingClass resolving class.
+ * @param templateProcessor template processor to be used.
+ * @return resolved viewable or {@code null} if the viewable cannot be resolved.
+ */
+ @SuppressWarnings("unchecked")
+ private ResolvedViewable resolveRelativeViewable(final Viewable viewable, final Class<?> resolvingClass,
+ final MediaType mediaType, final TemplateProcessor templateProcessor) {
+ final String path = TemplateHelper.getTemplateName(viewable);
+
+ // Find in directories.
+ for (Class c = resolvingClass; c != Object.class; c = c.getSuperclass()) {
+ final String absolutePath = TemplateHelper.getAbsolutePath(c, path, '/');
+ final Object resolvedTemplateObject = templateProcessor.resolve(absolutePath, mediaType);
+
+ if (resolvedTemplateObject != null) {
+ return new ResolvedViewable(templateProcessor, resolvedTemplateObject, viewable, c, mediaType);
+ }
+ }
+
+ // Find in flat files.
+ for (Class c = resolvingClass; c != Object.class; c = c.getSuperclass()) {
+ final String absolutePath = TemplateHelper.getAbsolutePath(c, path, '.');
+ final Object resolvedTemplateObject = templateProcessor.resolve(absolutePath, mediaType);
+
+ if (resolvedTemplateObject != null) {
+ return new ResolvedViewable(templateProcessor, resolvedTemplateObject, viewable, c, mediaType);
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateHelper.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateHelper.java
new file mode 100644
index 0000000..50190b0
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateHelper.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2013, 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.mvc.internal;
+
+import java.lang.annotation.Annotation;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Variant;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.internal.util.collection.Refs;
+import org.glassfish.jersey.message.internal.MediaTypes;
+import org.glassfish.jersey.message.internal.VariantSelector;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.mvc.MvcFeature;
+import org.glassfish.jersey.server.mvc.Template;
+import org.glassfish.jersey.server.mvc.Viewable;
+
+/**
+ * Helper class to provide some common functionality related to MVC.
+ *
+ * @author Michal Gajdos
+ */
+public final class TemplateHelper {
+
+ private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");
+
+ /**
+ * Return an absolute path to the given class where segments are separated using {@code delim} character and {@code path}
+ * is appended to this path.
+ *
+ * @param resourceClass class for which an absolute path should be obtained.
+ * @param path segment to be appended to the resulting path.
+ * @param delim character used for separating path segments.
+ * @return an absolute path to the resource class.
+ */
+ public static String getAbsolutePath(Class<?> resourceClass, String path, char delim) {
+ return '/' + resourceClass.getName().replace('.', '/').replace('$', delim) + delim + path;
+ }
+
+ /**
+ * Get media types for which the {@link org.glassfish.jersey.server.mvc.spi.ResolvedViewable resolved viewable} could be
+ * produced.
+ *
+ * @param containerRequest request to obtain acceptable media types.
+ * @param extendedUriInfo uri info to obtain resource method from and its producible media types.
+ * @param varyHeaderValue Vary header reference.
+ * @return list of producible media types.
+ */
+ public static List<MediaType> getProducibleMediaTypes(final ContainerRequest containerRequest,
+ final ExtendedUriInfo extendedUriInfo,
+ final Ref<String> varyHeaderValue) {
+ final List<MediaType> producedTypes = getResourceMethodProducibleTypes(extendedUriInfo);
+ final MediaType[] mediaTypes = producedTypes.toArray(new MediaType[producedTypes.size()]);
+
+ final List<Variant> variants = VariantSelector.selectVariants(
+ containerRequest, Variant.mediaTypes(mediaTypes).build(),
+ varyHeaderValue == null ? Refs.<String>emptyRef() : varyHeaderValue);
+
+ return variants.stream()
+ .map(variant -> MediaTypes.stripQualityParams(variant.getMediaType()))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Get template name from given {@link org.glassfish.jersey.server.mvc.Viewable viewable} or return {@code index} if the given
+ * viewable doesn't contain a valid template name.
+ *
+ * @param viewable viewable to obtain template name from.
+ * @return {@code non-null}, {@code non-empty} template name.
+ */
+ public static String getTemplateName(final Viewable viewable) {
+ return viewable.getTemplateName() == null || viewable.getTemplateName().isEmpty() ? "index" : viewable.getTemplateName();
+ }
+
+ /**
+ * Return a list of producible media types of the last matched resource method.
+ *
+ * @param extendedUriInfo uri info to obtain resource method from.
+ * @return list of producible media types of the last matched resource method.
+ */
+ private static List<MediaType> getResourceMethodProducibleTypes(final ExtendedUriInfo extendedUriInfo) {
+ if (extendedUriInfo.getMatchedResourceMethod() != null
+ && !extendedUriInfo.getMatchedResourceMethod().getProducedTypes().isEmpty()) {
+ return extendedUriInfo.getMatchedResourceMethod().getProducedTypes();
+ }
+ return Arrays.asList(MediaType.WILDCARD_TYPE);
+ }
+
+ /**
+ * Extract {@link org.glassfish.jersey.server.mvc.Template template} annotation from given list.
+ *
+ * @param annotations list of annotations.
+ * @return {@link org.glassfish.jersey.server.mvc.Template template} annotation or {@code null} if this annotation is not present.
+ */
+ public static Template getTemplateAnnotation(final Annotation[] annotations) {
+ if (annotations != null && annotations.length > 0) {
+ for (Annotation annotation : annotations) {
+ if (annotation instanceof Template) {
+ return (Template) annotation;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get output encoding from configuration.
+ * @param configuration Configuration.
+ * @param suffix Template processor suffix of the
+ * to configuration property {@link org.glassfish.jersey.server.mvc.MvcFeature#ENCODING}.
+ *
+ * @return Encoding read from configuration properties or a default encoding if no encoding is configured.
+ */
+ public static Charset getTemplateOutputEncoding(Configuration configuration, String suffix) {
+ final String enc = PropertiesHelper.getValue(configuration.getProperties(), MvcFeature.ENCODING + suffix,
+ String.class, null);
+ if (enc == null) {
+ return DEFAULT_ENCODING;
+ } else {
+ return Charset.forName(enc);
+ }
+ }
+
+ /**
+ * Prevents instantiation.
+ */
+ private TemplateHelper() {
+ }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateInflector.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateInflector.java
new file mode 100644
index 0000000..3673dae
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateInflector.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.mvc.internal;
+
+/**
+ * Interface providing information about implicit viewable resource classes.
+ *
+ * @author Michal Gajdos
+ * @since 2.3
+ */
+public interface TemplateInflector {
+
+ /**
+ * Get a class of the model used in template of implicit viewable.
+ *
+ * @return class of model used in template of implicit viewable.
+ */
+ public Class<?> getModelClass();
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateMethodInterceptor.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateMethodInterceptor.java
new file mode 100644
index 0000000..0fdfacb
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateMethodInterceptor.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.mvc.internal;
+
+import java.io.IOException;
+
+import javax.ws.rs.Priorities;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.ext.WriterInterceptor;
+import javax.ws.rs.ext.WriterInterceptorContext;
+
+import javax.annotation.Priority;
+
+import org.glassfish.jersey.server.mvc.Template;
+import org.glassfish.jersey.server.mvc.Viewable;
+
+/**
+ * Intercepts resource methods that are annotated by {@link Template template annotation} and does not return {@link Viewable}
+ * instances.
+ *
+ * @see org.glassfish.jersey.server.mvc.spi.ResolvedViewable
+ * @see ViewableMessageBodyWriter
+ *
+ * @author Michal Gajdos
+ */
+@Priority(Priorities.ENTITY_CODER)
+class TemplateMethodInterceptor implements WriterInterceptor {
+
+ @Override
+ public void aroundWriteTo(final WriterInterceptorContext context) throws IOException, WebApplicationException {
+ final Template template = TemplateHelper.getTemplateAnnotation(context.getAnnotations());
+ final Object entity = context.getEntity();
+
+ if (template != null && !(entity instanceof Viewable)) {
+ context.setType(Viewable.class);
+ context.setEntity(new Viewable(template.name(), entity));
+ }
+
+ context.proceed();
+ }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateModelProcessor.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateModelProcessor.java
new file mode 100644
index 0000000..8f96009
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateModelProcessor.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 2013, 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.mvc.internal;
+
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.Produces;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ResourceContext;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.glassfish.jersey.internal.Errors;
+import org.glassfish.jersey.internal.util.Producer;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.message.internal.MediaTypes;
+import org.glassfish.jersey.process.Inflector;
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.internal.inject.ConfiguredValidator;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.model.ModelProcessor;
+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.RuntimeResource;
+import org.glassfish.jersey.server.model.internal.ModelHelper;
+import org.glassfish.jersey.server.model.internal.ModelProcessorUtil;
+import org.glassfish.jersey.server.mvc.Template;
+import org.glassfish.jersey.server.mvc.Viewable;
+
+/**
+ * {@link ModelProcessor Model processor} enhancing (sub-)resources with {@value HttpMethod#GET} methods responsible of producing
+ * implicit {@link org.glassfish.jersey.server.mvc.Viewable viewables}.
+ * <p/>
+ * Note: Resource classes has to be annotated with {@link Template} annotation in order to be enhanced by this model processor.
+ *
+ * @author Michal Gajdos
+ * @see Template
+ */
+class TemplateModelProcessor implements ModelProcessor {
+
+ /**
+ * Path parameter representing implicit template name.
+ */
+ private static final String IMPLICIT_VIEW_PATH_PARAMETER = "implicit-view-path-parameter";
+ private static final String IMPLICIT_VIEW_PATH_PARAMETER_TEMPLATE = "{" + IMPLICIT_VIEW_PATH_PARAMETER + "}";
+
+ private final Provider<ResourceContext> resourceContextProvider;
+ private final Provider<ExtendedUriInfo> extendedUriInfoProvider;
+ private final Provider<ConfiguredValidator> validatorProvider;
+
+ /**
+ * Inflector producing response with {@link org.glassfish.jersey.server.mvc.spi.ResolvedViewable resolved viewable} where
+ * model is the resource class annotated with {@link Template} or 404 as its status code.
+ */
+ private class TemplateInflectorImpl implements TemplateInflector, Inflector<ContainerRequestContext, Response> {
+
+ private final String templateName;
+
+ private final Class<?> resourceClass;
+ private final Object resourceInstance;
+
+ private Class<?> modelClass;
+
+ /**
+ * Create enhancing template {@link Inflector inflector} method.
+ *
+ * @param templateName template name for the produced {@link org.glassfish.jersey.server.mvc.Viewable viewable}.
+ * @param resourceClass model class for the produced {@link org.glassfish.jersey.server.mvc.Viewable viewable}.
+ * Should not be {@code null}.
+ * @param resourceInstance model for the produced {@link org.glassfish.jersey.server.mvc.Viewable viewable}. May be
+ * {@code null}.
+ */
+ private TemplateInflectorImpl(final String templateName, final Class<?> resourceClass,
+ final Object resourceInstance) {
+ this.templateName = templateName;
+
+ this.resourceClass = resourceClass;
+ this.resourceInstance = resourceInstance;
+ }
+
+ @Override
+ public Response apply(ContainerRequestContext requestContext) {
+ final List<String> templateNames = getTemplateNames(requestContext);
+ final Object model = getModel(extendedUriInfoProvider.get());
+
+ // Validate resource class.
+ final ConfiguredValidator validator = validatorProvider.get();
+ if (validator != null) {
+ validator.validateResourceAndInputParams(model, null, null);
+ }
+
+ return Response.ok().entity(new ImplicitViewable(templateNames, model, resourceClass)).build();
+ }
+
+ @Override
+ public Class<?> getModelClass() {
+ return modelClass;
+ }
+
+ private Object setModelClass(final Object model) {
+ if (modelClass == null) {
+ modelClass = model.getClass();
+ }
+ return model;
+ }
+
+ /**
+ * Obtains a model object for a viewable.
+ *
+ * @param extendedUriInfo uri info to obtain last matched resource from.
+ * @return a model object.
+ */
+ private Object getModel(final ExtendedUriInfo extendedUriInfo) {
+ final List<Object> matchedResources = extendedUriInfo.getMatchedResources();
+
+ if (resourceInstance != null) {
+ return setModelClass(resourceInstance);
+ } else if (matchedResources.size() > 1) {
+ return setModelClass(matchedResources.get(1));
+ } else {
+ return setModelClass(resourceContextProvider.get().getResource(resourceClass));
+ }
+ }
+
+ /**
+ * Returns a list of template names to be considered as candidates for resolving
+ * {@link org.glassfish.jersey.server.mvc.Viewable viewable} into
+ * {@link org.glassfish.jersey.server.mvc.spi.ResolvedViewable resolved viewable}.
+ * <p/>
+ * Order of template names to be resolved is as follows:
+ * <ul>
+ * <li>{{@value #IMPLICIT_VIEW_PATH_PARAMETER}} value</li>
+ * <li>{@link org.glassfish.jersey.server.mvc.Template#name()}</li>
+ * <li>last sub-resource locator path</li>
+ * <li>index</li>
+ * </ul>
+ *
+ * @param requestContext request context to obtain {@link #IMPLICIT_VIEW_PATH_PARAMETER} value from.
+ * @return a non-empty list of template names.
+ */
+ private List<String> getTemplateNames(final ContainerRequestContext requestContext) {
+ final List<String> templateNames = new ArrayList<>();
+
+ // Template name extracted from path param.
+ final String pathTemplate = requestContext.getUriInfo().getPathParameters().getFirst(IMPLICIT_VIEW_PATH_PARAMETER);
+ if (pathTemplate != null) {
+ templateNames.add(pathTemplate);
+ }
+
+ // Annotation.
+ if (this.templateName != null && !"".equals(this.templateName)) {
+ templateNames.add(this.templateName);
+ }
+
+ // Sub-resource path.
+ final ExtendedUriInfo uriInfo = extendedUriInfoProvider.get();
+ final List<RuntimeResource> matchedRuntimeResources = uriInfo.getMatchedRuntimeResources();
+ if (matchedRuntimeResources.size() > 1) {
+ // > 1 to check that we matched sub-resource
+ final RuntimeResource lastMatchedRuntimeResource = matchedRuntimeResources.get(0);
+ final Resource lastMatchedResource = lastMatchedRuntimeResource.getResources().get(0);
+
+ String path = lastMatchedResource.getPath();
+
+ if (path != null && !IMPLICIT_VIEW_PATH_PARAMETER_TEMPLATE.equals(path)) {
+ path = path.charAt(0) == '/' ? path.substring(1, path.length()) : path;
+ templateNames.add(path);
+ }
+ }
+
+ // Index.
+ if (templateNames.isEmpty()) {
+ templateNames.add("index");
+ }
+
+ return templateNames;
+ }
+ }
+
+ /**
+ * Create a {@code TemplateModelProcessor} instance.
+ *
+ * @param resourceContextProvider (injected) resource context provider.
+ * @param validatorProvider Jersey extension of BeanValidation Validator.
+ * @param extendedUriInfoProvider (injected) extended uri info provider.
+ */
+ @Inject
+ TemplateModelProcessor(final Provider<ResourceContext> resourceContextProvider,
+ final Provider<ConfiguredValidator> validatorProvider,
+ final Provider<ExtendedUriInfo> extendedUriInfoProvider) {
+ this.resourceContextProvider = resourceContextProvider;
+ this.validatorProvider = validatorProvider;
+ this.extendedUriInfoProvider = extendedUriInfoProvider;
+ }
+
+ @Override
+ public ResourceModel processResourceModel(final ResourceModel resourceModel, final Configuration configuration) {
+ return processModel(resourceModel, false);
+ }
+
+ @Override
+ public ResourceModel processSubResource(final ResourceModel subResourceModel, final Configuration configuration) {
+ return processModel(subResourceModel, true);
+ }
+
+ /**
+ * Enhance {@link org.glassfish.jersey.server.model.RuntimeResource runtime resources} from the given
+ * {@link org.glassfish.jersey.server.model.ResourceModel resource model} with methods obtained via
+ * {@link #getEnhancingMethods(org.glassfish.jersey.server.model.RuntimeResource)}.
+ *
+ * @param resourceModel resource model with runtime resources to enhance.
+ * @param subResourceModel determines whether the resource model represents sub-resource.
+ * @return enhanced resource model.
+ */
+ private ResourceModel processModel(final ResourceModel resourceModel, final boolean subResourceModel) {
+ ResourceModel.Builder newModelBuilder = processTemplateAnnotatedInvocables(resourceModel, subResourceModel);
+
+ for (RuntimeResource resource : resourceModel.getRuntimeResourceModel().getRuntimeResources()) {
+ ModelProcessorUtil.enhanceResource(resource, newModelBuilder, getEnhancingMethods(resource), false);
+ }
+
+ return newModelBuilder.build();
+ }
+
+ /**
+ * Process all {@link Invocable invocables} and defines
+ * {@link org.glassfish.jersey.server.model.Invocable#getRoutingResponseType() routing response types}
+ * as {@link Viewable} for all methods annotated with {@link Template}.
+ *
+ * @param resourceModel resource model to process.
+ * @param subResourceModel determines whether the resource model represents sub-resource.
+ * @return Modified resource model.
+ */
+ private ResourceModel.Builder processTemplateAnnotatedInvocables(ResourceModel resourceModel,
+ final boolean subResourceModel) {
+ ResourceModel.Builder modelBuilder = new ResourceModel.Builder(subResourceModel);
+ for (Resource resource : resourceModel.getResources()) {
+ Resource newResource = processResource(resource);
+ modelBuilder.addResource(newResource);
+ }
+ return modelBuilder;
+ }
+
+ private Resource processResource(Resource resource) {
+ Resource.Builder resourceBuilder = Resource.builder(resource.getPath());
+ for (ResourceMethod resourceMethod : resource.getResourceMethods()) {
+ ResourceMethod.Builder builder = resourceBuilder.addMethod(resourceMethod);
+ if (resourceMethod.getInvocable().getHandlingMethod().isAnnotationPresent(Template.class)) {
+ builder.routingResponseType(Viewable.class);
+ }
+ }
+ if (resource.getResourceLocator() != null) {
+ resourceBuilder.addMethod(resource.getResourceLocator());
+ }
+
+ for (Resource child : resource.getChildResources()) {
+ resourceBuilder.addChildResource(processResource(child));
+ }
+ return resourceBuilder.build();
+
+ }
+
+ /**
+ * Returns a list of enhancing methods for a given {@link org.glassfish.jersey.server.model.RuntimeResource runtime
+ * resource}.
+ *
+ * @param runtimeResource runtime resource to create enhancing methods for.
+ * @return list of enhancing methods.
+ */
+ private List<ModelProcessorUtil.Method> getEnhancingMethods(final RuntimeResource runtimeResource) {
+ final List<ModelProcessorUtil.Method> newMethods = new ArrayList<>();
+
+ for (final Resource resource : runtimeResource.getResources()) {
+ // Handler classes.
+ for (final Class<?> handlerClass : resource.getHandlerClasses()) {
+ createEnhancingMethods(handlerClass, null, newMethods);
+ }
+
+ // Names - if there are no handler classes / instances.
+ if (resource.getHandlerClasses().isEmpty() && resource.getHandlerInstances().isEmpty()) {
+ for (String resourceName : resource.getNames()) {
+ final Class<Object> resourceClass = AccessController
+ .doPrivileged(ReflectionHelper.classForNamePA(resourceName));
+ if (resourceClass != null) {
+ createEnhancingMethods(resourceClass, null, newMethods);
+ }
+ }
+ }
+
+ // Handler instances.
+ Errors.process(new Producer<Void>() {
+ @Override
+ public Void call() {
+ for (final Object handlerInstance : resource.getHandlerInstances()) {
+ final Class<?> handlerInstanceClass = handlerInstance.getClass();
+
+ if (!resource.getHandlerClasses().contains(handlerInstanceClass)) {
+ createEnhancingMethods(handlerInstanceClass, handlerInstance, newMethods);
+ } else {
+ Errors.warning(resource,
+ LocalizationMessages.TEMPLATE_HANDLER_ALREADY_ENHANCED(handlerInstanceClass));
+ }
+ }
+
+ return null;
+ }
+ });
+ }
+
+ return newMethods;
+ }
+
+ /**
+ * Creates enhancing methods for given resource.
+ *
+ * @param resourceClass resource class for which enhancing methods should be created.
+ * @param resourceInstance resource instance for which enhancing methods should be created. May be {@code null}.
+ * @param newMethods list to store new methods into.
+ */
+ private void createEnhancingMethods(final Class<?> resourceClass, final Object resourceInstance,
+ final List<ModelProcessorUtil.Method> newMethods) {
+ final Template template = resourceClass.getAnnotation(Template.class);
+
+ if (template != null) {
+ final Class<?> annotatedResourceClass = ModelHelper.getAnnotatedResourceClass(resourceClass);
+
+ final List<MediaType> produces = MediaTypes
+ .createQualitySourceMediaTypes(annotatedResourceClass.getAnnotation(Produces.class));
+ final List<MediaType> consumes = MediaTypes.createFrom(annotatedResourceClass.getAnnotation(Consumes.class));
+
+ final TemplateInflectorImpl inflector = new TemplateInflectorImpl(template.name(),
+ resourceClass, resourceInstance);
+
+ newMethods.add(new ModelProcessorUtil.Method(HttpMethod.GET, consumes, produces, inflector));
+ newMethods.add(new ModelProcessorUtil.Method(IMPLICIT_VIEW_PATH_PARAMETER_TEMPLATE, HttpMethod.GET,
+ consumes, produces, inflector));
+ }
+ }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ViewableMessageBodyWriter.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ViewableMessageBodyWriter.java
new file mode 100644
index 0000000..61820ef
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ViewableMessageBodyWriter.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2013, 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.mvc.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.server.mvc.spi.ResolvedViewable;
+import org.glassfish.jersey.server.mvc.spi.TemplateProcessor;
+import org.glassfish.jersey.server.mvc.spi.ViewableContext;
+import org.glassfish.jersey.server.mvc.spi.ViewableContextException;
+
+/**
+ * {@link javax.ws.rs.ext.MessageBodyWriter Message body writer} for {@link org.glassfish.jersey.server.mvc.Viewable viewable}
+ * entities.
+ *
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+@Provider
+@ConstrainedTo(RuntimeType.SERVER)
+final class ViewableMessageBodyWriter implements MessageBodyWriter<Viewable> {
+
+ @Inject
+ private InjectionManager injectionManager;
+
+ @Context
+ private javax.inject.Provider<ExtendedUriInfo> extendedUriInfoProvider;
+ @Context
+ private javax.inject.Provider<ContainerRequest> requestProvider;
+ @Context
+ private javax.inject.Provider<ResourceInfo> resourceInfoProvider;
+
+ private static final Logger LOGGER = Logger.getLogger(ViewableMessageBodyWriter.class.getName());
+
+
+ @Override
+ public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return Viewable.class.isAssignableFrom(type);
+ }
+
+ @Override
+ public long getSize(final Viewable viewable, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(final Viewable viewable,
+ final Class<?> type,
+ final Type genericType,
+ final Annotation[] annotations,
+ final MediaType mediaType,
+ final MultivaluedMap<String, Object> httpHeaders,
+ final OutputStream entityStream) throws IOException, WebApplicationException {
+
+ try {
+ final ResolvedViewable resolvedViewable = resolve(viewable);
+ if (resolvedViewable == null) {
+ final String message = LocalizationMessages.TEMPLATE_NAME_COULD_NOT_BE_RESOLVED(viewable.getTemplateName());
+ throw new WebApplicationException(new ProcessingException(message), Response.Status.NOT_FOUND);
+ }
+
+ httpHeaders.putSingle(HttpHeaders.CONTENT_TYPE, resolvedViewable.getMediaType());
+ resolvedViewable.writeTo(entityStream, httpHeaders);
+ } catch (ViewableContextException vce) {
+ throw new NotFoundException(vce);
+ }
+ }
+
+ /**
+ * Resolve the given {@link org.glassfish.jersey.server.mvc.Viewable viewable} using
+ * {@link org.glassfish.jersey.server.mvc.spi.ViewableContext}.
+ *
+ * @param viewable viewable to be resolved.
+ * @return resolved viewable or {@code null}, if the viewable cannot be resolved.
+ */
+ private ResolvedViewable resolve(final Viewable viewable) {
+ if (viewable instanceof ResolvedViewable) {
+ return (ResolvedViewable) viewable;
+ } else {
+ final ViewableContext viewableContext = getViewableContext();
+ final Set<TemplateProcessor> templateProcessors = getTemplateProcessors();
+
+ final List<MediaType> producibleMediaTypes = TemplateHelper
+ .getProducibleMediaTypes(requestProvider.get(), extendedUriInfoProvider.get(), null);
+
+ final Class<?> resourceClass = resourceInfoProvider.get().getResourceClass();
+ if (viewable instanceof ImplicitViewable) {
+ // Template Names.
+ final ImplicitViewable implicitViewable = (ImplicitViewable) viewable;
+
+ for (final String templateName : implicitViewable.getTemplateNames()) {
+ final Viewable simpleViewable = new Viewable(templateName, viewable.getModel());
+
+ final ResolvedViewable resolvedViewable = resolve(simpleViewable, producibleMediaTypes,
+ implicitViewable.getResolvingClass(), viewableContext, templateProcessors);
+
+ if (resolvedViewable != null) {
+ return resolvedViewable;
+ }
+ }
+ } else {
+ return resolve(viewable, producibleMediaTypes, resourceClass, viewableContext, templateProcessors);
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * Resolve given {@link org.glassfish.jersey.server.mvc.Viewable viewable} for a list of {@link javax.ws.rs.core.MediaType mediaTypes} and a {@link Class resolvingClass}
+ * using given {@link org.glassfish.jersey.server.mvc.spi.ViewableContext viewableContext} and a set of {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor templateProcessors}
+ *
+ * @param viewable viewable to be resolved.
+ * @param mediaTypes producible media types.
+ * @param resolvingClass non-null resolving class.
+ * @param viewableContext viewable context.
+ * @param templateProcessors collection of available template processors.
+ * @return resolved viewable or {@code null}, if the viewable cannot be resolved.
+ */
+ private ResolvedViewable resolve(final Viewable viewable, final List<MediaType> mediaTypes, final Class<?> resolvingClass,
+ final ViewableContext viewableContext, final Set<TemplateProcessor> templateProcessors) {
+ for (TemplateProcessor templateProcessor : templateProcessors) {
+ for (final MediaType mediaType : mediaTypes) {
+ final ResolvedViewable resolvedViewable = viewableContext
+ .resolveViewable(viewable, mediaType, resolvingClass, templateProcessor);
+
+ if (resolvedViewable != null) {
+ return resolvedViewable;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get a {@link java.util.LinkedHashSet collection} of available template processors.
+ *
+ * @return set of template processors.
+ */
+ private Set<TemplateProcessor> getTemplateProcessors() {
+ final Set<TemplateProcessor> templateProcessors = new LinkedHashSet<>();
+
+ templateProcessors.addAll(Providers.getCustomProviders(injectionManager, TemplateProcessor.class));
+ templateProcessors.addAll(Providers.getProviders(injectionManager, TemplateProcessor.class));
+
+ return templateProcessors;
+ }
+
+ /**
+ * Get {@link org.glassfish.jersey.server.mvc.spi.ViewableContext viewable context}. User defined (custom) contexts have higher priority than the default ones
+ * (i.e. {@link ResolvingViewableContext}).
+ *
+ * @return {@code non-null} viewable context.
+ */
+ private ViewableContext getViewableContext() {
+ final Set<ViewableContext> customProviders = Providers.getCustomProviders(injectionManager, ViewableContext.class);
+ if (!customProviders.isEmpty()) {
+ return customProviders.iterator().next();
+ }
+ return Providers.getProviders(injectionManager, ViewableContext.class).iterator().next();
+ }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/package-info.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/package-info.java
new file mode 100644
index 0000000..52d1952
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/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 server-side internal MVC (Model View Controller) classes.
+ */
+package org.glassfish.jersey.server.mvc.internal;
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/package-info.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/package-info.java
new file mode 100644
index 0000000..eec0145
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/package-info.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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
+ */
+
+/**
+ * Provides support for Model, View and Controller (MVC).
+ * <p/>
+ * Given the MVC pattern the Controller corresponds to a resource class, the View to a template referenced by a template name,
+ * and the Model to a Java object (or a Java bean).
+ * <p/>
+ * A resource method of a resource class may return an instance of {@link org.glassfish.jersey.server.mvc.Viewable} that
+ * encapsulates the template name and the model. In this respect the instance of{@link org.glassfish.jersey.server.mvc.Viewable}
+ * is the response entity. Such a viewable response entity may be set in contexts other than a resource method but for the
+ * purposes of this section the focus is on resource methods.
+ * <p/>
+ * The {@link org.glassfish.jersey.server.mvc.Viewable}, returned by a resource method,
+ * will be processed such that the template name is resolved to a template reference that identifies a template capable of
+ * being processed by an appropriate view processor.
+ * <br/>
+ * The view processor then processes template given the model to produce a response entity that is returned to the client.
+ * <p/>
+ * For example, the template name could reference a Java Server Page (JSP) and the model will be accessible to that JSP. The
+ * JSP view processor will process the JSP resulting in an HTML document that is returned as the response entity. (See later
+ * for more details.)
+ * <p/>
+ * Two forms of returning {@link org.glassfish.jersey.server.mvc.Viewable} instances are supported: explicit; and implicit.
+ */
+package org.glassfish.jersey.server.mvc;
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractErrorTemplateMapper.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractErrorTemplateMapper.java
new file mode 100644
index 0000000..c513445
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractErrorTemplateMapper.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2013, 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.mvc.spi;
+
+import javax.ws.rs.core.Response;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.model.ResourceMethod;
+import org.glassfish.jersey.server.mvc.ErrorTemplate;
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.server.mvc.internal.TemplateInflector;
+import org.glassfish.jersey.spi.ExtendedExceptionMapper;
+
+/**
+ * Default implementation of {@link ExtendedExceptionMapper} used to declare special handling for exception types that should be
+ * processed by MVC.
+ * <p/>
+ * Extensions should override {@link #getErrorStatus(Throwable)} and {@link #getErrorModel(Throwable)} to provide a response
+ * status and model derived from a raised throwable.
+ * <p/>
+ * By default every {@link Exception exception} is mapped and used as a model in a viewable and passed to the MVC runtime for
+ * further processing.
+ *
+ * @param <T> A type of the exception processed by the exception mapper.
+ * @author Michal Gajdos
+ * @since 2.3
+ */
+@Singleton
+public abstract class AbstractErrorTemplateMapper<T extends Throwable> implements ExtendedExceptionMapper<T> {
+
+ @Inject
+ private javax.inject.Provider<ExtendedUriInfo> uriInfoProvider;
+
+ @Override
+ public final boolean isMappable(final T throwable) {
+ return getErrorTemplate() != null;
+ }
+
+ /**
+ * Get an {@link ErrorTemplate} annotation from resource method / class the throwable was raised from.
+ *
+ * @return an error template annotation or {@code null} if the method is not annotated.
+ */
+ private ErrorTemplate getErrorTemplate() {
+ final ExtendedUriInfo uriInfo = uriInfoProvider.get();
+ final ResourceMethod matchedResourceMethod = uriInfo.getMatchedResourceMethod();
+
+ if (matchedResourceMethod != null) {
+ final Invocable invocable = matchedResourceMethod.getInvocable();
+
+ ErrorTemplate errorTemplate = invocable.getHandlingMethod().getAnnotation(ErrorTemplate.class);
+ if (errorTemplate == null) {
+ Class<?> handlerClass = invocable.getHandler().getHandlerClass();
+
+ if (invocable.isInflector() && TemplateInflector.class
+ .isAssignableFrom(invocable.getHandler().getHandlerClass())) {
+
+ handlerClass = ((TemplateInflector) invocable.getHandler().getInstance(null)).getModelClass();
+ }
+
+ errorTemplate = handlerClass.getAnnotation(ErrorTemplate.class);
+ }
+
+ return errorTemplate;
+ }
+
+ return null;
+ }
+
+ @Override
+ public final Response toResponse(final T throwable) {
+ final ErrorTemplate error = getErrorTemplate();
+ final String templateName = "".equals(error.name()) ? "index" : error.name();
+
+ return Response
+ .status(getErrorStatus(throwable))
+ .entity(new Viewable(templateName, getErrorModel(throwable)))
+ .build();
+ }
+
+ /**
+ * Get a model for error template. Default value is the {@code throwable} itself.
+ *
+ * @param throwable throwable raised during processing a resource method.
+ * @return a model for error template.
+ */
+ protected Object getErrorModel(final T throwable) {
+ return throwable;
+ }
+
+ /**
+ * Get a response status of to-be-processed error template. Default value is {@link Response.Status#OK}.
+ *
+ * @param throwable throwable raised during processing a resource method.
+ * @return response status of error response.
+ */
+ protected Response.Status getErrorStatus(final T throwable) {
+ return Response.Status.OK;
+ }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractTemplateProcessor.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractTemplateProcessor.java
new file mode 100644
index 0000000..049ea9a
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractTemplateProcessor.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2013, 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.mvc.spi;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import javax.servlet.ServletContext;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.internal.util.collection.DataStructures;
+import org.glassfish.jersey.internal.util.collection.Value;
+import org.glassfish.jersey.server.mvc.MvcFeature;
+import org.glassfish.jersey.server.mvc.internal.LocalizationMessages;
+import org.glassfish.jersey.server.mvc.internal.TemplateHelper;
+
+/**
+ * Default implementation of {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor template processor} that can be used to
+ * implement support for custom templating engines. The class currently recognizes following properties:
+ * <ul>
+ * <li>{@link org.glassfish.jersey.server.mvc.MvcFeature#TEMPLATE_BASE_PATH}</li>
+ * <li>{@link org.glassfish.jersey.server.mvc.MvcFeature#CACHE_TEMPLATES}</li>
+ * <li>{@link org.glassfish.jersey.server.mvc.MvcFeature#TEMPLATE_OBJECT_FACTORY}</li>
+ * </ul>
+ * If any of the properties are not supported by particular template processor then this fact should be mentioned in documentation
+ * of the template processor.
+ *
+ * @author Michal Gajdos
+ */
+public abstract class AbstractTemplateProcessor<T> implements TemplateProcessor<T> {
+
+ private static final Logger LOGGER = Logger.getLogger(AbstractTemplateProcessor.class.getName());
+
+ private final ConcurrentMap<String, T> cache;
+
+ private final String suffix;
+ private final Configuration config;
+ private final ServletContext servletContext;
+
+ private final String basePath;
+ private final Set<String> supportedExtensions;
+ private final Charset encoding;
+
+ /**
+ * Create an instance of the processor with injected {@link javax.ws.rs.core.Configuration config} and
+ * (optional) {@link ServletContext servlet context}.
+ *
+ * @param config configuration to configure this processor from.
+ * @param servletContext (optional) servlet context to obtain template resources from.
+ * @param propertySuffix suffix to distinguish properties for current template processor.
+ * @param supportedExtensions supported template file extensions.
+ */
+ public AbstractTemplateProcessor(final Configuration config, final ServletContext servletContext,
+ final String propertySuffix, final String... supportedExtensions) {
+ this.config = config;
+ this.suffix = '.' + propertySuffix;
+
+ this.servletContext = servletContext;
+ this.supportedExtensions =
+ Arrays.stream(supportedExtensions)
+ .map(input -> {
+ input = input.toLowerCase();
+ return input.startsWith(".") ? input : "." + input;
+ })
+ .collect(Collectors.toSet());
+
+ // Resolve property values.
+ final Map<String, Object> properties = config.getProperties();
+
+ // Base Path.
+ String basePath = PropertiesHelper.getValue(properties, MvcFeature.TEMPLATE_BASE_PATH + suffix, String.class, null);
+ if (basePath == null) {
+ basePath = PropertiesHelper.getValue(properties, MvcFeature.TEMPLATE_BASE_PATH, "", null);
+ }
+ this.basePath = basePath;
+
+ // Cache.
+ Boolean cacheEnabled = PropertiesHelper.getValue(properties, MvcFeature.CACHE_TEMPLATES + suffix, Boolean.class, null);
+ if (cacheEnabled == null) {
+ cacheEnabled = PropertiesHelper.getValue(properties, MvcFeature.CACHE_TEMPLATES, false, null);
+ }
+ this.cache = cacheEnabled ? DataStructures.<String, T>createConcurrentMap() : null;
+ this.encoding = TemplateHelper.getTemplateOutputEncoding(config, suffix);
+ }
+
+ /**
+ * Return base path for current template processor.
+ *
+ * @return base path or an empty string.
+ */
+ protected String getBasePath() {
+ return basePath;
+ }
+
+ /**
+ * Return current servlet context, if present.
+ *
+ * @return servlet context instance or {@code null}.
+ */
+ protected ServletContext getServletContext() {
+ return servletContext;
+ }
+
+ @Override
+ public T resolve(final String name, final MediaType mediaType) {
+ // Look into the cache if enabled.
+ if (cache != null) {
+ if (!cache.containsKey(name)) {
+ cache.putIfAbsent(name, resolve(name));
+ }
+ return cache.get(name);
+ }
+
+ return resolve(name);
+ }
+
+ /**
+ * Resolve a template name to a template reference.
+ *
+ * @param name the template name.
+ * @return the template reference, otherwise {@code null} if the template name cannot be resolved.
+ */
+ private T resolve(final String name) {
+ for (final String template : getTemplatePaths(name)) {
+ Reader reader = null;
+
+ // ServletContext.
+ if (servletContext != null) {
+ //"The path must begin with a "/"".
+ final String path = template.startsWith("/") ? template : "/" + template;
+ final InputStream stream = servletContext.getResourceAsStream(path);
+ reader = stream != null ? new InputStreamReader(stream) : null;
+ }
+
+ // Classloader.
+ if (reader == null) {
+ InputStream stream = getClass().getResourceAsStream(template);
+ if (stream == null) {
+ stream = getClass().getClassLoader().getResourceAsStream(template);
+ }
+ reader = stream != null ? new InputStreamReader(stream) : null;
+ }
+
+ // File-system path.
+ if (reader == null) {
+ try {
+ reader = new InputStreamReader(new FileInputStream(template), encoding);
+ } catch (final FileNotFoundException fnfe) {
+ // NOOP.
+ }
+ }
+
+ if (reader != null) {
+ try {
+ return resolve(template, reader);
+ } catch (final Exception e) {
+ LOGGER.log(Level.WARNING, LocalizationMessages.TEMPLATE_RESOLVE_ERROR(template), e);
+ } finally {
+ try {
+ reader.close();
+ } catch (final IOException e) {
+ LOGGER.log(Level.WARNING, LocalizationMessages.TEMPLATE_ERROR_CLOSING_READER(), e);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Resolve given template path and/or reader to a template reference object.
+ *
+ * @param templatePath resolved template path (incl. base path and suffix).
+ * @param reader reader containing template character stream.
+ * @return non-{@code null} template reference object.
+ * @throws Exception if an exception occurred during resolving.
+ */
+ protected abstract T resolve(final String templatePath, final Reader reader) throws Exception;
+
+ /**
+ * Return collection of possible template paths (included basePath and suffix).
+ *
+ * @param name the template name.
+ * @return collection of possible template paths.
+ */
+ private Collection<String> getTemplatePaths(final String name) {
+ final String lowerName = name.toLowerCase();
+ final String templatePath = basePath.endsWith("/") ? basePath + name.substring(1) : basePath + name;
+
+ // Check whether the given name ends with supported suffix.
+ for (final String extension : supportedExtensions) {
+ if (lowerName.endsWith(extension)) {
+ return Collections.singleton(templatePath);
+ }
+ }
+
+ return supportedExtensions.stream().map(input -> templatePath + input).collect(Collectors.toSet());
+ }
+
+ /**
+ * Retrieve a template object factory. The factory is, at first, looked for in
+ * {@link javax.ws.rs.core.Configuration configuration} and if not found, given default value is used.
+ *
+ * @param createInstance function that delegates a creation and an initialization to injection manager.
+ * @param type type of requested template object factory.
+ * @param defaultValue default value to be used if no factory reference is present in configuration.
+ * @param <F> type of requested template object factory.
+ * @return non-{@code null} template object factory.
+ */
+ protected <F> F getTemplateObjectFactory(Function<Class<?>, ?> createInstance, Class<F> type, Value<F> defaultValue) {
+ final Object objectFactoryProperty = config.getProperty(MvcFeature.TEMPLATE_OBJECT_FACTORY + suffix);
+
+ if (objectFactoryProperty != null) {
+ if (type.isAssignableFrom(objectFactoryProperty.getClass())) {
+ return type.cast(objectFactoryProperty);
+ } else {
+ Class<?> factoryClass = null;
+
+ if (objectFactoryProperty instanceof String) {
+ factoryClass = ReflectionHelper.classForNamePA((String) objectFactoryProperty).run();
+ } else if (objectFactoryProperty instanceof Class<?>) {
+ factoryClass = (Class<?>) objectFactoryProperty;
+ }
+
+ if (factoryClass != null) {
+ if (type.isAssignableFrom(factoryClass)) {
+ return type.cast(createInstance.apply(factoryClass));
+ } else {
+ LOGGER.log(Level.CONFIG, LocalizationMessages.WRONG_TEMPLATE_OBJECT_FACTORY(factoryClass, type));
+ }
+ }
+ }
+ }
+
+ return defaultValue.get();
+ }
+
+ /**
+ * Set the {@link HttpHeaders#CONTENT_TYPE} header to the {@code httpHeaders} based on {@code mediaType} and
+ * {@link #getEncoding() default encoding} defined in this processor. If {@code mediaType} defines encoding
+ * then this encoding will be used otherwise the default processor encoding is used. The chosen encoding
+ * is returned from the method.
+ *
+ * @param mediaType Media type of the entity.
+ * @param httpHeaders Http headers.
+ * @return Selected encoding.
+ */
+ protected Charset setContentType(final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders) {
+ final Charset encoding;
+
+ final String charset = mediaType.getParameters().get(MediaType.CHARSET_PARAMETER);
+ final MediaType finalMediaType;
+ if (charset == null) {
+ encoding = getEncoding();
+ final HashMap<String, String> params = new HashMap<>(mediaType.getParameters());
+ params.put(MediaType.CHARSET_PARAMETER, encoding.name());
+ finalMediaType = new MediaType(mediaType.getType(), mediaType.getSubtype(), params);
+ } else {
+ encoding = Charset.forName(charset);
+ finalMediaType = mediaType;
+ }
+ final ArrayList<Object> typeList = new ArrayList<>(1);
+ typeList.add(finalMediaType.toString());
+ httpHeaders.put(HttpHeaders.CONTENT_TYPE, typeList);
+ return encoding;
+ }
+
+ /**
+ * Get the output encoding.
+ *
+ * @return Not-{@code null} encoding.
+ */
+ protected Charset getEncoding() {
+ return encoding;
+ }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ResolvedViewable.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ResolvedViewable.java
new file mode 100644
index 0000000..069b822
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ResolvedViewable.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2010, 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.mvc.spi;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.glassfish.jersey.server.mvc.Viewable;
+
+/**
+ * A resolved {@link org.glassfish.jersey.server.mvc.Viewable viewable}.
+ * <p/>
+ * A resolved viewable is obtained from the resolving methods on {@link org.glassfish.jersey.server.mvc.spi.ViewableContext}
+ * and has associated with it a {@link TemplateProcessor} that is capable of processing a template identified by a template
+ * reference.
+ *
+ * @param <T> the type of the resolved template object.
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+public final class ResolvedViewable<T> extends Viewable {
+
+ private final TemplateProcessor<T> viewProcessor;
+
+ private final T templateReference;
+
+ private final MediaType mediaType;
+
+ private final Class<?> resolvingClass;
+
+
+ /**
+ * Create a resolved viewable.
+ *
+ * @param viewProcessor the view processor that resolved a template name to a template reference.
+ * @param templateReference the template reference.
+ * @param viewable the viewable that is resolved.
+ * @param mediaType media type the {@code templateReference} should be transformed into.
+ */
+ public ResolvedViewable(TemplateProcessor<T> viewProcessor, T templateReference, Viewable viewable, MediaType mediaType) {
+ this(viewProcessor, templateReference, viewable, null, mediaType);
+ }
+
+ /**
+ * Create a resolved viewable.
+ *
+ * @param viewProcessor the view processor that resolved a template name to a template reference.
+ * @param templateReference the template reference.
+ * @param viewable the viewable that is resolved.
+ * @param resolvingClass the resolving class that was used to resolve a relative template name into an absolute template name.
+ * @param mediaType media type the {@code templateReference} should be transformed into.
+ */
+ public ResolvedViewable(TemplateProcessor<T> viewProcessor, T templateReference, Viewable viewable,
+ Class<?> resolvingClass, MediaType mediaType) {
+ super(viewable.getTemplateName(), viewable.getModel());
+
+ this.viewProcessor = viewProcessor;
+ this.templateReference = templateReference;
+ this.mediaType = mediaType;
+ this.resolvingClass = resolvingClass;
+ }
+
+ /**
+ * Write the resolved viewable.
+ * <p/>
+ * This method defers to
+ * {@link TemplateProcessor#writeTo(Object, org.glassfish.jersey.server.mvc.Viewable, javax.ws.rs.core.MediaType,
+ * javax.ws.rs.core.MultivaluedMap, java.io.OutputStream)}
+ * to write the viewable utilizing the template reference.
+ *
+ * @param out the output stream that the view processor writes to.
+ * @throws java.io.IOException if there was an error processing the template.
+ */
+ public void writeTo(OutputStream out, final MultivaluedMap<String, Object> httpHeaders) throws IOException {
+ viewProcessor.writeTo(templateReference, this, mediaType, httpHeaders, out);
+ }
+
+ /**
+ * Get the media type for which the {@link TemplateProcessor view processor} resolved the template reference.
+ *
+ * @return final {@link javax.ws.rs.core.MediaType media type} of the resolved viewable.
+ */
+ public MediaType getMediaType() {
+ return mediaType;
+ }
+
+
+ /**
+ * Get resolving class.
+ *
+ * @return Resolving class.
+ */
+ public Class<?> getResolvingClass() {
+ return resolvingClass;
+ }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/TemplateProcessor.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/TemplateProcessor.java
new file mode 100644
index 0000000..9d31792
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/TemplateProcessor.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.server.mvc.spi;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * A view processor.
+ * <p/>
+ * Implementations of this interface shall be capable of resolving a
+ * template name (+ media type) to a template reference that identifies a template supported
+ * by the implementation. And, processing the template, identified by template
+ * reference and media type, the results of which are written to an output stream.
+ * <p/>
+ * Implementations can register a view processor as a provider, for
+ * example, annotating the implementation class with {@link javax.ws.rs.ext.Provider}
+ * or registering an implementing class or instance as a singleton with
+ * {@link org.glassfish.jersey.server.ResourceConfig} or {@link javax.ws.rs.core.Application}.
+ * <p/>
+ * Such view processors could be JSP view processors (supported by the
+ * Jersey servlet and filter implementations) or say Freemarker or Velocity
+ * view processors (not implemented).
+ *
+ * @param <T> the type of the template object.
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+@Contract
+@ConstrainedTo(RuntimeType.SERVER)
+public interface TemplateProcessor<T> {
+
+ /**
+ * Resolve a template name to a template reference.
+ *
+ * @param name the template name.
+ * @param mediaType requested media type of the template.
+ * @return the template reference, otherwise {@code null} if the template name cannot be resolved.
+ */
+ public T resolve(String name, MediaType mediaType);
+
+ /**
+ * Process a template and write the result to an output stream.
+ *
+ * @param templateReference the template reference. This is obtained by calling the {@link #resolve(String,
+ * javax.ws.rs.core.MediaType)} method with a template name and media type.
+ * @param viewable the viewable that contains the model to be passed to the template.
+ * @param mediaType media type the {@code templateReference} should be transformed into.
+ * @param httpHeaders http headers that will be send in the response. Headers can be modified to
+ * influence response headers before the the first byte is written
+ * to the {@code out}. After the response buffer is committed the headers modification
+ * has no effect. Template processor can for example set the content type of
+ * the response.
+ * @param out the output stream to write the result of processing the template.
+ * @throws java.io.IOException if there was an error processing the template.
+ *
+ * @since 2.7
+ */
+ public void writeTo(T templateReference, Viewable viewable, MediaType mediaType,
+ final MultivaluedMap<String, Object> httpHeaders, OutputStream out) throws IOException;
+
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ViewableContext.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ViewableContext.java
new file mode 100644
index 0000000..a6fc2fb
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ViewableContext.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.mvc.spi;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * The context for resolving an instance of {@link org.glassfish.jersey.server.mvc.Viewable} to an instance of {@link
+ * ResolvedViewable}.
+ * <p/>
+ * Note:
+ * {@link ViewableContext#resolveViewable(org.glassfish.jersey.server.mvc.Viewable, javax.ws.rs.core.MediaType, Class, TemplateProcessor)}
+ * method may be called multiple times (combination of all the calculated possible media types of the response with all found
+ * {@link TemplateProcessor template processors}).
+ *
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+@Contract
+@ConstrainedTo(RuntimeType.SERVER)
+public interface ViewableContext {
+
+ /**
+ * Resolve given {@link org.glassfish.jersey.server.mvc.Viewable viewable} using {@link javax.ws.rs.core.MediaType mediaType},
+ * {@code resourceClass} and {@link TemplateProcessor templateProcessor}.
+ * <p/>
+ * If the template name of the viewable is not absolute then the given {@code resourceClass} may be utilized to resolve
+ * the relative template name into an absolute template name.
+ * <br/>
+ * <ul>
+ * {@code resourceClass} contains class of the matched resource.
+ * </ul>
+ *
+ * @param viewable viewable to be resolved.
+ * @param mediaType media type the viewable may be transformed into.
+ * @param resourceClass resource class.
+ * @param templateProcessor template processor to be used.
+ * @return resolved viewable or {@code null} if the viewable cannot be resolved.
+ */
+ public ResolvedViewable resolveViewable(final Viewable viewable, final MediaType mediaType, final Class<?> resourceClass,
+ final TemplateProcessor templateProcessor);
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ViewableContextException.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ViewableContextException.java
new file mode 100644
index 0000000..cf8c093
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ViewableContextException.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2010, 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.mvc.spi;
+
+import org.glassfish.jersey.server.ContainerException;
+
+/**
+ * A runtime exception associated with errors when resolving a {@link org.glassfish.jersey.server.mvc.Viewable} to a {@link
+ * ResolvedViewable} by methods on {@link ViewableContext}.
+ *
+ * @author Paul Sandoz
+ */
+@SuppressWarnings("UnusedDeclaration")
+public class ViewableContextException extends ContainerException {
+
+ /**
+ * Construct a new instance with the supplied message.
+ *
+ * @param message the message.
+ */
+ public ViewableContextException(String message) {
+ super(message);
+ }
+
+ /**
+ * Construct a new instance with the supplied message and cause.
+ *
+ * @param message the message.
+ * @param cause the Throwable that caused the exception to be thrown.
+ */
+ public ViewableContextException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Construct a new instance with the supplied cause.
+ *
+ * @param cause the Throwable that caused the exception to be thrown.
+ */
+ public ViewableContextException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/package-info.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/package-info.java
new file mode 100644
index 0000000..ae1d3a7
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2013, 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
+ */
+
+/**
+ * Provides support for view aspect of model view controller and templates that
+ * produce views.
+ */
+package org.glassfish.jersey.server.mvc.spi;
diff --git a/ext/mvc/src/main/resources/org/glassfish/jersey/server/mvc/internal/localization.properties b/ext/mvc/src/main/resources/org/glassfish/jersey/server/mvc/internal/localization.properties
new file mode 100644
index 0000000..5b0eaa2
--- /dev/null
+++ b/ext/mvc/src/main/resources/org/glassfish/jersey/server/mvc/internal/localization.properties
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse 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
+#
+
+template.handler.already.enhanced=Handler class "{0}" has been already enhanced. Skipping enhancing handler instance.
+template.model.of.view.cannot.be.null=The model of the view MUST not be null.
+template.name.could.not.be.resolved=The template name "{0}" could not be resolved to a fully qualified template name.
+template.name.must.not.be.null=The template name MUST not be null.
+# {0} - list of template names, e.g. ["sub", "index"], {1} - list of paths, e.g. ["/views", "/org/glassfish/jersey/server/mvc/TemplateResource"], {2} - list of media types, e.g. ["text/plain"]
+template.names.could.not.be.resolved=Neither of the template names ({0}) could be resolved to a fully qualified template name. (paths: {1}, media-types: {2})
+template.no.matching.resource.available=There is no last matching resource available.
+template.resolve.error=An exception occurred during resolving the {0} template.
+template.resolving.class.cannot.be.null=Resolving class MUST not be null.
+template.error.closing.reader=File reader was not closed properly.
+wrong.template.object.factory=Provided template object factory class, {0}, is not assignable from required type {1}.
+
diff --git a/ext/pom.xml b/ext/pom.xml
new file mode 100644
index 0000000..0e56abf
--- /dev/null
+++ b/ext/pom.xml
@@ -0,0 +1,78 @@
+<?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.ext</groupId>
+ <artifactId>project</artifactId>
+ <packaging>pom</packaging>
+ <name>jersey-extensions</name>
+
+ <description>
+ Jersey extension modules providing utilities, filters as well as integrations
+ with external frameworks (Guice, Spring, Freemarker, etc.) and languages
+ (Scala, Groovy, etc.).
+
+ NOTE: Jersey security extensions have their own dedicated security umbrella
+ project.
+ </description>
+
+ <modules>
+ <module>bean-validation</module>
+ <module>cdi</module>
+ <module>entity-filtering</module>
+ <module>metainf-services</module>
+ <module>mvc</module>
+ <module>mvc-bean-validation</module>
+ <module>mvc-freemarker</module>
+ <module>mvc-jsp</module>
+ <module>mvc-mustache</module>
+ <module>proxy-client</module>
+ <module>rx</module>
+ <module>servlet-portability</module>
+ <module>spring4</module>
+ <module>wadl-doclet</module>
+ </modules>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/ext/proxy-client/pom.xml b/ext/proxy-client/pom.xml
new file mode 100644
index 0000000..4477b0f
--- /dev/null
+++ b/ext/proxy-client/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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.ext</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-proxy-client</artifactId>
+ <name>jersey-ext-proxy-client</name>
+ <description>
+ Jersey extension module providing support for (proxy-based) high-level client API.
+ </description>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>org.glassfish.jersey.client.proxy.*;version=${project.version}</Export-Package>
+ </instructions>
+ <unpackBundle>true</unpackBundle>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ <version>${project.version}</version>
+ </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>
+ </dependencies>
+</project>
diff --git a/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/WebResourceFactory.java b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/WebResourceFactory.java
new file mode 100644
index 0000000..d457c0b
--- /dev/null
+++ b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/WebResourceFactory.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.client.proxy;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.security.AccessController;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.Form;
+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.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Factory for client-side representation of a resource.
+ * See the <a href="package-summary.html">package overview</a>
+ * for an example on how to use this class.
+ *
+ * @author Martin Matula
+ */
+public final class WebResourceFactory implements InvocationHandler {
+
+ private static final String[] EMPTY = {};
+
+ private final WebTarget target;
+ private final MultivaluedMap<String, Object> headers;
+ private final List<Cookie> cookies;
+ private final Form form;
+
+ private static final MultivaluedMap<String, Object> EMPTY_HEADERS = new MultivaluedHashMap<>();
+ private static final Form EMPTY_FORM = new Form();
+ private static final List<Class> PARAM_ANNOTATION_CLASSES = Arrays.<Class>asList(PathParam.class, QueryParam.class,
+ HeaderParam.class, CookieParam.class, MatrixParam.class, FormParam.class);
+
+ /**
+ * Creates a new client-side representation of a resource described by
+ * the interface passed in the first argument.
+ * <p/>
+ * Calling this method has the same effect as calling {@code WebResourceFactory.newResource(resourceInterface, rootTarget,
+ *false)}.
+ *
+ * @param <C> Type of the resource to be created.
+ * @param resourceInterface Interface describing the resource to be created.
+ * @param target WebTarget pointing to the resource or the parent of the resource.
+ * @return Instance of a class implementing the resource interface that can
+ * be used for making requests to the server.
+ */
+ public static <C> C newResource(final Class<C> resourceInterface, final WebTarget target) {
+ return newResource(resourceInterface, target, false, EMPTY_HEADERS, Collections.<Cookie>emptyList(), EMPTY_FORM);
+ }
+
+ /**
+ * Creates a new client-side representation of a resource described by
+ * the interface passed in the first argument.
+ *
+ * @param <C> Type of the resource to be created.
+ * @param resourceInterface Interface describing the resource to be created.
+ * @param target WebTarget pointing to the resource or the parent of the resource.
+ * @param ignoreResourcePath If set to true, ignores path annotation on the resource interface (this is used when creating
+ * sub-resources)
+ * @param headers Header params collected from parent resources (used when creating a sub-resource)
+ * @param cookies Cookie params collected from parent resources (used when creating a sub-resource)
+ * @param form Form params collected from parent resources (used when creating a sub-resource)
+ * @return Instance of a class implementing the resource interface that can
+ * be used for making requests to the server.
+ */
+ @SuppressWarnings("unchecked")
+ public static <C> C newResource(final Class<C> resourceInterface,
+ final WebTarget target,
+ final boolean ignoreResourcePath,
+ final MultivaluedMap<String, Object> headers,
+ final List<Cookie> cookies,
+ final Form form) {
+
+ return (C) Proxy.newProxyInstance(AccessController.doPrivileged(ReflectionHelper.getClassLoaderPA(resourceInterface)),
+ new Class[] {resourceInterface},
+ new WebResourceFactory(ignoreResourcePath ? target : addPathFromAnnotation(resourceInterface, target),
+ headers, cookies, form));
+ }
+
+ private WebResourceFactory(final WebTarget target, final MultivaluedMap<String, Object> headers,
+ final List<Cookie> cookies, final Form form) {
+ this.target = target;
+ this.headers = headers;
+ this.cookies = cookies;
+ this.form = form;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+ if (args == null && method.getName().equals("toString")) {
+ return toString();
+ }
+
+ if (args == null && method.getName().equals("hashCode")) {
+ //unique instance in the JVM, and no need to override
+ return hashCode();
+ }
+
+ if (args != null && args.length == 1 && method.getName().equals("equals")) {
+ //unique instance in the JVM, and no need to override
+ return equals(args[0]);
+ }
+
+ // get the interface describing the resource
+ final Class<?> proxyIfc = proxy.getClass().getInterfaces()[0];
+
+ // response type
+ final Class<?> responseType = method.getReturnType();
+
+ // determine method name
+ String httpMethod = getHttpMethodName(method);
+ if (httpMethod == null) {
+ for (final Annotation ann : method.getAnnotations()) {
+ httpMethod = getHttpMethodName(ann.annotationType());
+ if (httpMethod != null) {
+ break;
+ }
+ }
+ }
+
+ // create a new UriBuilder appending the @Path attached to the method
+ WebTarget newTarget = addPathFromAnnotation(method, target);
+
+ if (httpMethod == null) {
+ if (newTarget == target) {
+ // no path annotation on the method -> fail
+ throw new UnsupportedOperationException("Not a resource method.");
+ } else if (!responseType.isInterface()) {
+ // the method is a subresource locator, but returns class,
+ // not interface - can't help here
+ throw new UnsupportedOperationException("Return type not an interface");
+ }
+ }
+
+ // process method params (build maps of (Path|Form|Cookie|Matrix|Header..)Params
+ // and extract entity type
+ final MultivaluedHashMap<String, Object> headers = new MultivaluedHashMap<String, Object>(this.headers);
+ final LinkedList<Cookie> cookies = new LinkedList<>(this.cookies);
+ final Form form = new Form();
+ form.asMap().putAll(this.form.asMap());
+ final Annotation[][] paramAnns = method.getParameterAnnotations();
+ Object entity = null;
+ Type entityType = null;
+ for (int i = 0; i < paramAnns.length; i++) {
+ final Map<Class, Annotation> anns = new HashMap<>();
+ for (final Annotation ann : paramAnns[i]) {
+ anns.put(ann.annotationType(), ann);
+ }
+ Annotation ann;
+ Object value = args[i];
+ if (!hasAnyParamAnnotation(anns)) {
+ entityType = method.getGenericParameterTypes()[i];
+ entity = value;
+ } else {
+ if (value == null && (ann = anns.get(DefaultValue.class)) != null) {
+ value = ((DefaultValue) ann).value();
+ }
+
+ if (value != null) {
+ if ((ann = anns.get(PathParam.class)) != null) {
+ newTarget = newTarget.resolveTemplate(((PathParam) ann).value(), value);
+ } else if ((ann = anns.get((QueryParam.class))) != null) {
+ if (value instanceof Collection) {
+ newTarget = newTarget.queryParam(((QueryParam) ann).value(), convert((Collection) value));
+ } else {
+ newTarget = newTarget.queryParam(((QueryParam) ann).value(), value);
+ }
+ } else if ((ann = anns.get((HeaderParam.class))) != null) {
+ if (value instanceof Collection) {
+ headers.addAll(((HeaderParam) ann).value(), convert((Collection) value));
+ } else {
+ headers.addAll(((HeaderParam) ann).value(), value);
+ }
+
+ } else if ((ann = anns.get((CookieParam.class))) != null) {
+ final String name = ((CookieParam) ann).value();
+ Cookie c;
+ if (value instanceof Collection) {
+ for (final Object v : ((Collection) value)) {
+ if (!(v instanceof Cookie)) {
+ c = new Cookie(name, v.toString());
+ } else {
+ c = (Cookie) v;
+ if (!name.equals(((Cookie) v).getName())) {
+ // is this the right thing to do? or should I fail? or ignore the difference?
+ c = new Cookie(name, c.getValue(), c.getPath(), c.getDomain(), c.getVersion());
+ }
+ }
+ cookies.add(c);
+ }
+ } else {
+ if (!(value instanceof Cookie)) {
+ cookies.add(new Cookie(name, value.toString()));
+ } else {
+ c = (Cookie) value;
+ if (!name.equals(((Cookie) value).getName())) {
+ // is this the right thing to do? or should I fail? or ignore the difference?
+ cookies.add(new Cookie(name, c.getValue(), c.getPath(), c.getDomain(), c.getVersion()));
+ }
+ }
+ }
+ } else if ((ann = anns.get((MatrixParam.class))) != null) {
+ if (value instanceof Collection) {
+ newTarget = newTarget.matrixParam(((MatrixParam) ann).value(), convert((Collection) value));
+ } else {
+ newTarget = newTarget.matrixParam(((MatrixParam) ann).value(), value);
+ }
+ } else if ((ann = anns.get((FormParam.class))) != null) {
+ if (value instanceof Collection) {
+ for (final Object v : ((Collection) value)) {
+ form.param(((FormParam) ann).value(), v.toString());
+ }
+ } else {
+ form.param(((FormParam) ann).value(), value.toString());
+ }
+ }
+ }
+ }
+ }
+
+ if (httpMethod == null) {
+ // the method is a subresource locator
+ return WebResourceFactory.newResource(responseType, newTarget, true, headers, cookies, form);
+ }
+
+ // accepted media types
+ Produces produces = method.getAnnotation(Produces.class);
+ if (produces == null) {
+ produces = proxyIfc.getAnnotation(Produces.class);
+ }
+ final String[] accepts = (produces == null) ? EMPTY : produces.value();
+
+ // determine content type
+ String contentType = null;
+ if (entity != null) {
+ final List<Object> contentTypeEntries = headers.get(HttpHeaders.CONTENT_TYPE);
+ if ((contentTypeEntries != null) && (!contentTypeEntries.isEmpty())) {
+ contentType = contentTypeEntries.get(0).toString();
+ } else {
+ Consumes consumes = method.getAnnotation(Consumes.class);
+ if (consumes == null) {
+ consumes = proxyIfc.getAnnotation(Consumes.class);
+ }
+ if (consumes != null && consumes.value().length > 0) {
+ contentType = consumes.value()[0];
+ }
+ }
+ }
+
+ Invocation.Builder builder = newTarget.request()
+ .headers(headers) // this resets all headers so do this first
+ .accept(accepts); // if @Produces is defined, propagate values into Accept header; empty array is NO-OP
+
+ for (final Cookie c : cookies) {
+ builder = builder.cookie(c);
+ }
+
+ final Object result;
+
+ if (entity == null && !form.asMap().isEmpty()) {
+ entity = form;
+ contentType = MediaType.APPLICATION_FORM_URLENCODED;
+ } else {
+ if (contentType == null) {
+ contentType = MediaType.APPLICATION_OCTET_STREAM;
+ }
+ if (!form.asMap().isEmpty()) {
+ if (entity instanceof Form) {
+ ((Form) entity).asMap().putAll(form.asMap());
+ } else {
+ // TODO: should at least log some warning here
+ }
+ }
+ }
+
+ final GenericType responseGenericType = new GenericType(method.getGenericReturnType());
+ if (entity != null) {
+ if (entityType instanceof ParameterizedType) {
+ entity = new GenericEntity(entity, entityType);
+ }
+ result = builder.method(httpMethod, Entity.entity(entity, contentType), responseGenericType);
+ } else {
+ result = builder.method(httpMethod, responseGenericType);
+ }
+
+ return result;
+ }
+
+ private boolean hasAnyParamAnnotation(final Map<Class, Annotation> anns) {
+ for (final Class paramAnnotationClass : PARAM_ANNOTATION_CLASSES) {
+ if (anns.containsKey(paramAnnotationClass)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Object[] convert(final Collection value) {
+ return value.toArray();
+ }
+
+ private static WebTarget addPathFromAnnotation(final AnnotatedElement ae, WebTarget target) {
+ final Path p = ae.getAnnotation(Path.class);
+ if (p != null) {
+ target = target.path(p.value());
+ }
+ return target;
+ }
+
+ @Override
+ public String toString() {
+ return target.toString();
+ }
+
+ private static String getHttpMethodName(final AnnotatedElement ae) {
+ final HttpMethod a = ae.getAnnotation(HttpMethod.class);
+ return a == null ? null : a.value();
+ }
+}
diff --git a/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/package-info.java b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/package-info.java
new file mode 100644
index 0000000..4a957dc
--- /dev/null
+++ b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/package-info.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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 package defines a high-level (proxy-based) client API.
+ * The API enables utilization of the server-side JAX-RS annotations
+ * to describe the server-side resources and dynamically generate client-side
+ * proxy objects for them.
+ * <p>
+ * Consider a server which exposes a resource at http://localhost:8080. The resource
+ * can be described by the following interface:
+ * </p>
+ *
+ * <pre>
+ * @Path("myresource")
+ * public interface MyResourceIfc {
+ * @GET
+ * @Produces("text/plain")
+ * String get();
+ *
+ * @POST
+ * @Consumes("application/xml")
+ * @Produces("application/xml")
+ * MyBean postEcho(MyBean bean);
+ *
+ * @Path("{id}")
+ * @GET
+ * @Produces("text/plain")
+ * String getById(@PathParam("id") String id);
+ * }
+ * </pre>
+ *
+ * <p>
+ * You can use <a href="WebResourceFactory.html">WebResourceFactory</a> class defined
+ * in this package to access the server-side resource using this interface.
+ * Here is an example:
+ * </p>
+ *
+ * <pre>
+ * Client client = ClientBuilder.newClient();
+ * WebTarget target = client.target("http://localhost:8080/");
+ * MyResourceIfc resource = WebResourceFactory.newResource(MyResourceIfc.class, target);
+ *
+ * String responseFromGet = resource.get();
+ * MyBean responseFromPost = resource.postEcho(myBeanInstance);
+ * String responseFromGetById = resource.getById("abc");
+ * </pre>
+ */
+package org.glassfish.jersey.client.proxy;
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyBean.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyBean.java
new file mode 100644
index 0000000..099aebf
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyBean.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.client.proxy;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class MyBean {
+ public String name;
+}
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResource.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResource.java
new file mode 100644
index 0000000..00f3922
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResource.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.client.proxy;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+
+public class MyResource implements MyResourceIfc {
+
+ @Context HttpHeaders headers;
+
+ @Override
+ public String getIt() {
+ return "Got it!";
+ }
+
+ @Override
+ public List<MyBean> postIt(List<MyBean> entity) {
+ return entity;
+ }
+
+ @Override
+ public MyBean postValid(@Valid MyBean entity) {
+ return entity;
+ }
+
+ @Override
+ public String getId(String id) {
+ return id;
+ }
+
+ @Override
+ public String getByName(String name) {
+ return name;
+ }
+
+ @Override
+ public String getByNameCookie(String name) {
+ return name;
+ }
+
+ @Override
+ public String getByNameHeader(String name) {
+ return name;
+ }
+
+ @Override
+ public String getByNameMatrix(String name) {
+ return name;
+ }
+
+ @Override
+ public String postByNameFormParam(String name) {
+ return name;
+ }
+
+ @Override
+ public String getByNameList(List<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String getByNameSet(Set<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String getByNameSortedSet(SortedSet<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String getByNameCookieList(List<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String getByNameCookieSet(Set<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String getByNameCookieSortedSet(SortedSet<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String getByNameHeaderList(List<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String getByNameHeaderSet(Set<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String getByNameHeaderSortedSet(SortedSet<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String getByNameMatrixList(List<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String getByNameMatrixSet(Set<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String getByNameMatrixSortedSet(SortedSet<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String postByNameFormList(List<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String postByNameFormSet(Set<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public String postByNameFormSortedSet(SortedSet<String> name) {
+ return name.size() + ":" + name;
+ }
+
+ @Override
+ public MySubResourceIfc getSubResource() {
+ return new MySubResource();
+ }
+
+ @Override
+ public boolean isAcceptHeaderValid(HttpHeaders headers) {
+ List<MediaType> accepts = headers.getAcceptableMediaTypes();
+ return accepts.contains(MediaType.TEXT_PLAIN_TYPE) && accepts.contains(MediaType.TEXT_XML_TYPE);
+ }
+
+ @Override
+ public String putIt(MyBean dummyBean) {
+ return headers.getHeaderString(HttpHeaders.CONTENT_TYPE);
+ }
+}
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResourceIfc.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResourceIfc.java
new file mode 100644
index 0000000..2b7d6fa
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResourceIfc.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.client.proxy;
+
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+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.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+
+@Path("myresource")
+public interface MyResourceIfc {
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getIt();
+
+ @POST
+ @Consumes({MediaType.APPLICATION_XML})
+ @Produces({MediaType.APPLICATION_XML})
+ List<MyBean> postIt(List<MyBean> entity);
+
+ @POST
+ @Path("valid")
+ @Consumes({MediaType.APPLICATION_XML})
+ @Produces({MediaType.APPLICATION_XML})
+ MyBean postValid(@Valid MyBean entity);
+
+ @Path("{id}")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getId(@PathParam("id") String id);
+
+ @Path("query")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByName(@QueryParam("name") String name);
+
+ @Path("cookie")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameCookie(@CookieParam("cookie-name") String name);
+
+ @Path("header")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameHeader(@HeaderParam("header-name") String name);
+
+ @Path("matrix")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameMatrix(@MatrixParam("matrix-name") String name);
+
+ @Path("form")
+ @POST
+ @Produces(MediaType.TEXT_PLAIN)
+ String postByNameFormParam(@FormParam("form-name") String name);
+
+
+ @Path("query-list")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameList(@QueryParam("name-list") List<String> name);
+
+ @Path("query-set")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameSet(@QueryParam("name-set") Set<String> name);
+
+ @Path("query-sortedset")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameSortedSet(@QueryParam("name-sorted") SortedSet<String> name);
+
+ @Path("cookie-list")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameCookieList(@CookieParam("cookie-name-list") List<String> name);
+
+ @Path("cookie-set")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameCookieSet(@CookieParam("cookie-name-set") Set<String> name);
+
+ @Path("cookie-sortedset")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameCookieSortedSet(@CookieParam("cookie-name-sorted") SortedSet<String> name);
+
+ @Path("header-list")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameHeaderList(@HeaderParam("header-name-list") List<String> name);
+
+ @Path("header-set")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameHeaderSet(@HeaderParam("header-name-set") Set<String> name);
+
+ @Path("header-sortedset")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameHeaderSortedSet(@HeaderParam("header-name-sorted") SortedSet<String> name);
+
+ @Path("matrix-list")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameMatrixList(@MatrixParam("matrix-name-list") List<String> name);
+
+ @Path("matrix-set")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameMatrixSet(@MatrixParam("matrix-name-set") Set<String> name);
+
+ @Path("matrix-sortedset")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ String getByNameMatrixSortedSet(@MatrixParam("matrix-name-sorted") SortedSet<String> name);
+
+ @Path("form-list")
+ @POST
+ @Produces(MediaType.TEXT_PLAIN)
+ String postByNameFormList(@FormParam("form-name-list") List<String> name);
+
+ @Path("form-set")
+ @POST
+ @Produces(MediaType.TEXT_PLAIN)
+ String postByNameFormSet(@FormParam("form-name-set") Set<String> name);
+
+ @Path("form-sortedset")
+ @POST
+ @Produces(MediaType.TEXT_PLAIN)
+ String postByNameFormSortedSet(@FormParam("form-name-sorted") SortedSet<String> name);
+
+ @Path("subresource")
+ MySubResourceIfc getSubResource();
+
+ @Path("isAcceptHeaderValid")
+ @GET
+ @Produces({MediaType.TEXT_PLAIN, MediaType.TEXT_XML})
+ boolean isAcceptHeaderValid(@Context HttpHeaders headers);
+
+ @Path("putIt")
+ @PUT
+ @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+ String putIt(MyBean dummyBean);
+}
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubResource.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubResource.java
new file mode 100644
index 0000000..04772e9
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubResource.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.client.proxy;
+
+/**
+ *
+ * @author Martin Matula
+ */
+public class MySubResource implements MySubResourceIfc {
+ @Override
+ public MyBean getMyBean() {
+ MyBean bean = new MyBean();
+ bean.name = "Got it!";
+ return bean;
+ }
+}
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubResourceIfc.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubResourceIfc.java
new file mode 100644
index 0000000..ccaa16b
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubResourceIfc.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.client.proxy;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ *
+ * @author Martin Matula
+ */
+public interface MySubResourceIfc {
+ @GET
+ @Produces(MediaType.APPLICATION_XML)
+ public MyBean getMyBean();
+}
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/Valid.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/Valid.java
new file mode 100644
index 0000000..8cc343a
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/Valid.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.client.proxy;
+
+public @interface Valid {
+}
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/WebResourceFactoryTest.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/WebResourceFactoryTest.java
new file mode 100644
index 0000000..1f51f17
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/WebResourceFactoryTest.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.client.proxy;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Martin Matula
+ */
+public class WebResourceFactoryTest extends JerseyTest {
+
+ private MyResourceIfc resource;
+ private MyResourceIfc resource2;
+ private MyResourceIfc resourceWithXML;
+
+ @Override
+ protected ResourceConfig configure() {
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.jdkhttp.JdkHttpServerTestContainerFactory
+ // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.simple.SimpleTestContainerFactory
+ enable(TestProperties.LOG_TRAFFIC);
+ // enable(TestProperties.DUMP_ENTITY);
+ return new ResourceConfig(MyResource.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ resource = WebResourceFactory.newResource(MyResourceIfc.class, target());
+ resource2 = WebResourceFactory.newResource(MyResourceIfc.class, target());
+
+ final MultivaluedMap<String, Object> headers = new MultivaluedHashMap<>(1);
+ headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
+ resourceWithXML = WebResourceFactory
+ .newResource(MyResourceIfc.class, target(), false, headers, Collections.<Cookie>emptyList(), new Form());
+ }
+
+ @Test
+ public void testGetIt() {
+ assertEquals("Got it!", resource.getIt());
+ }
+
+ @Test
+ public void testPostIt() {
+ final MyBean bean = new MyBean();
+ bean.name = "Ahoj";
+ assertEquals("Ahoj", resource.postIt(Collections.singletonList(bean)).get(0).name);
+ }
+
+ @Test
+ public void testPostValid() {
+ final MyBean bean = new MyBean();
+ bean.name = "Ahoj";
+ assertEquals("Ahoj", resource.postValid(bean).name);
+ }
+
+ @Test
+ public void testPathParam() {
+ assertEquals("jouda", resource.getId("jouda"));
+ }
+
+ @Test
+ public void testQueryParam() {
+ assertEquals("jiri", resource.getByName("jiri"));
+ }
+
+ @Test
+ public void testFormParam() {
+ assertEquals("jiri", resource.postByNameFormParam("jiri"));
+ }
+
+ @Test
+ public void testCookieParam() {
+ assertEquals("jiri", resource.getByNameCookie("jiri"));
+ }
+
+ @Test
+ public void testHeaderParam() {
+ assertEquals("jiri", resource.getByNameHeader("jiri"));
+ }
+
+ @Test
+ public void testMatrixParam() {
+ assertEquals("jiri", resource.getByNameMatrix("jiri"));
+ }
+
+ @Test
+ public void testSubResource() {
+ assertEquals("Got it!", resource.getSubResource().getMyBean().name);
+ }
+
+ @Test
+ public void testQueryParamsAsList() {
+ final List<String> list = new ArrayList<>();
+ list.add("a");
+ list.add("bb");
+ list.add("ccc");
+
+ assertEquals("3:[a, bb, ccc]", resource.getByNameList(list));
+ }
+
+ @Test
+ public void testQueryParamsAsSet() {
+ final Set<String> set = new HashSet<>();
+ set.add("a");
+ set.add("bb");
+ set.add("ccc");
+
+ final String result = resource.getByNameSet(set);
+ checkSet(result);
+ }
+
+ @Test
+ public void testQueryParamsAsSortedSet() {
+ final SortedSet<String> set = new TreeSet<>();
+ set.add("a");
+ set.add("bb");
+ set.add("ccc");
+
+ final String result = resource.getByNameSortedSet(set);
+ assertEquals("3:[a, bb, ccc]", result);
+ }
+
+ @Test
+ @Ignore("See issue JERSEY-2441")
+ public void testHeaderCookieAsList() {
+ final List<String> list = new ArrayList<>();
+ list.add("a");
+ list.add("bb");
+ list.add("ccc");
+
+ assertEquals("3:[a, bb, ccc]", resource.getByNameCookieList(list));
+ }
+
+ @Test
+ @Ignore("See issue JERSEY-2441")
+ public void testHeaderCookieAsSet() {
+ final Set<String> set = new HashSet<>();
+ set.add("a");
+ set.add("bb");
+ set.add("ccc");
+
+ final String result = resource.getByNameCookieSet(set);
+ checkSet(result);
+ }
+
+ @Test
+ @Ignore("See issue JERSEY-2441")
+ public void testHeaderCookieAsSortedSet() {
+ final SortedSet<String> set = new TreeSet<>();
+ set.add("a");
+ set.add("bb");
+ set.add("ccc");
+
+ final String result = resource.getByNameCookieSortedSet(set);
+ assertEquals("3:[a, bb, ccc]", result);
+ }
+
+ /**
+ * This cannot work with jersey now. Server side parses header params only if they are send as more
+ * lines in the request. Jersey has currently no possibility to do so. See JERSEY-2263.
+ */
+ @Test
+ @Ignore("See issue JERSEY-2263")
+ public void testHeaderParamsAsList() {
+ final List<String> list = new ArrayList<>();
+ list.add("a");
+ list.add("bb");
+ list.add("ccc");
+
+ assertEquals("3:[a, bb, ccc]", resource.getByNameHeaderList(list));
+ }
+
+ @Test
+ @Ignore("See issue JERSEY-2263")
+ public void testHeaderParamsAsSet() {
+ final Set<String> set = new HashSet<>();
+ set.add("a");
+ set.add("bb");
+ set.add("ccc");
+
+ final String result = resource.getByNameHeaderSet(set);
+ checkSet(result);
+ }
+
+ @Test
+ @Ignore("See issue JERSEY-2263")
+ public void testHeaderParamsAsSortedSet() {
+ final SortedSet<String> set = new TreeSet<>();
+ set.add("a");
+ set.add("bb");
+ set.add("ccc");
+
+ final String result = resource.getByNameHeaderSortedSet(set);
+ assertEquals("3:[a, bb, ccc]", result);
+ }
+
+ @Test
+ public void testMatrixParamsAsList() {
+ final List<String> list = new ArrayList<>();
+ list.add("a");
+ list.add("bb");
+ list.add("ccc");
+
+ assertEquals("3:[a, bb, ccc]", resource.getByNameMatrixList(list));
+ }
+
+ @Test
+ public void testMatrixParamsAsSet() {
+ final Set<String> set = new HashSet<>();
+ set.add("a");
+ set.add("bb");
+ set.add("ccc");
+
+ final String result = resource.getByNameMatrixSet(set);
+ checkSet(result);
+ }
+
+ @Test
+ public void testMatrixParamsAsSortedSet() {
+ final SortedSet<String> set = new TreeSet<>();
+ set.add("a");
+ set.add("bb");
+ set.add("ccc");
+
+ final String result = resource.getByNameMatrixSortedSet(set);
+ assertEquals("3:[a, bb, ccc]", result);
+ }
+
+ private void checkSet(final String result) {
+ assertTrue("Set does not contain 3 items.", result.startsWith("3:["));
+ assertTrue("Set does not contain 'a' item.", result.contains("a"));
+ assertTrue("Set does not contain 'bb' item.", result.contains("bb"));
+ assertTrue("Set does not contain 'ccc' item.", result.contains("ccc"));
+ }
+
+ @Test
+ public void testFormParamsAsList() {
+ final List<String> list = new ArrayList<>();
+ list.add("a");
+ list.add("bb");
+ list.add("ccc");
+
+ assertEquals("3:[a, bb, ccc]", resource.postByNameFormList(list));
+ }
+
+ @Test
+ public void testFormParamsAsSet() {
+ final Set<String> set = new HashSet<>();
+ set.add("a");
+ set.add("bb");
+ set.add("ccc");
+
+ final String result = resource.postByNameFormSet(set);
+ checkSet(result);
+ }
+
+ @Test
+ public void testFormParamsAsSortedSet() {
+ final SortedSet<String> set = new TreeSet<>();
+ set.add("a");
+ set.add("bb");
+ set.add("ccc");
+
+ final String result = resource.postByNameFormSortedSet(set);
+ assertEquals("3:[a, bb, ccc]", result);
+ }
+
+ @Test
+ public void testAcceptHeader() {
+ assertTrue("Accept HTTP header does not match @Produces annotation", resource.isAcceptHeaderValid(null));
+ }
+
+ @Test
+ public void testPutWithExplicitContentType() {
+ assertEquals("Content-Type HTTP header does not match explicitly provided type", resourceWithXML.putIt(new MyBean()),
+ MediaType.APPLICATION_XML);
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ final String actual = resource.toString();
+ final String expected = target().path("myresource").toString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testHashCode() throws Exception {
+ int h1 = resource.hashCode();
+ int h2 = resource2.hashCode();
+ assertNotEquals("The hash codes should not match", h1, h2);
+ }
+
+ @Test
+ public void testEquals() {
+ assertFalse("The two resource instances should not be considered equals as they are unique", resource.equals(resource2));
+ }
+}
diff --git a/ext/rx/pom.xml b/ext/rx/pom.xml
new file mode 100644
index 0000000..3826cc7
--- /dev/null
+++ b/ext/rx/pom.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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.ext</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.ext.rx</groupId>
+ <artifactId>project</artifactId>
+ <packaging>pom</packaging>
+ <name>jersey-ext-rx</name>
+
+ <description>
+ Jersey incubator for Reactive (Rx) modules that are in a prototyping or research stage,
+ not ready to be released as part of Jersey (yet).
+ </description>
+
+ <modules>
+ <module>rx-client-guava</module>
+ <module>rx-client-rxjava</module>
+ <module>rx-client-rxjava2</module>
+ </modules>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/ext/rx/rx-client-guava/pom.xml b/ext/rx/rx-client-guava/pom.xml
new file mode 100644
index 0000000..8061dd1
--- /dev/null
+++ b/ext/rx/rx-client-guava/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!--
+
+ 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
+
+-->
+
+<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.ext.rx</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-rx-client-guava</artifactId>
+ <name>jersey-ext-rx-client-guava</name>
+
+ <description>Jersey Reactive Client - Guava (ListenableFuture) provider.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/JerseyRxListenableFutureInvoker.java b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/JerseyRxListenableFutureInvoker.java
new file mode 100644
index 0000000..bb7a98a
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/JerseyRxListenableFutureInvoker.java
@@ -0,0 +1,74 @@
+/*
+ * 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.client.rx.guava;
+
+import java.util.concurrent.ExecutorService;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.SyncInvoker;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.client.AbstractRxInvoker;
+import org.glassfish.jersey.internal.util.collection.LazyValue;
+import org.glassfish.jersey.internal.util.collection.Value;
+import org.glassfish.jersey.internal.util.collection.Values;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+/**
+ * Implementation of Reactive Invoker for {@code ListenableFuture}.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @since 2.13
+ */
+final class JerseyRxListenableFutureInvoker extends AbstractRxInvoker<ListenableFuture> implements RxListenableFutureInvoker {
+
+ private static final LazyValue<ListeningExecutorService> DEFAULT_EXECUTOR_SERVICE =
+ Values.lazy(new Value<ListeningExecutorService>() {
+ @Override
+ public ListeningExecutorService get() {
+ return MoreExecutors.newDirectExecutorService();
+ }
+ });
+
+ private final ListeningExecutorService service;
+
+ JerseyRxListenableFutureInvoker(final SyncInvoker syncInvoker, final ExecutorService executor) {
+ super(syncInvoker, executor);
+
+ if (executor == null) {
+ // TODO: use JAX-RS client scheduler
+ // TODO: https://java.net/jira/browse/JAX_RS_SPEC-523
+ service = DEFAULT_EXECUTOR_SERVICE.get();
+ } else {
+ service = MoreExecutors.listeningDecorator(executor);
+ }
+ }
+
+ @Override
+ public <T> ListenableFuture<T> method(final String name, final Entity<?> entity, final Class<T> responseType) {
+ return service.submit(() -> getSyncInvoker().method(name, entity, responseType));
+ }
+
+ @Override
+ public <T> ListenableFuture<T> method(final String name, final Entity<?> entity, final GenericType<T> responseType) {
+ return service.submit(() -> getSyncInvoker().method(name, entity, responseType));
+ }
+}
diff --git a/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureInvoker.java b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureInvoker.java
new file mode 100644
index 0000000..d9b20c5
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureInvoker.java
@@ -0,0 +1,108 @@
+/*
+ * 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.client.rx.guava;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.RxInvoker;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Reactive invoker providing support for {@link com.google.common.util.concurrent.ListenableFuture ListenableFuture} from Guava.
+ *
+ * @author Michal Gajdos
+ * @since 2.13
+ */
+public interface RxListenableFutureInvoker extends RxInvoker<ListenableFuture> {
+
+ @Override
+ public ListenableFuture<Response> get();
+
+ @Override
+ public <T> ListenableFuture<T> get(Class<T> responseType);
+
+ @Override
+ public <T> ListenableFuture<T> get(GenericType<T> responseType);
+
+ @Override
+ public ListenableFuture<Response> put(Entity<?> entity);
+
+ @Override
+ public <T> ListenableFuture<T> put(Entity<?> entity, Class<T> clazz);
+
+ @Override
+ public <T> ListenableFuture<T> put(Entity<?> entity, GenericType<T> type);
+
+ @Override
+ public ListenableFuture<Response> post(Entity<?> entity);
+
+ @Override
+ public <T> ListenableFuture<T> post(Entity<?> entity, Class<T> clazz);
+
+ @Override
+ public <T> ListenableFuture<T> post(Entity<?> entity, GenericType<T> type);
+
+ @Override
+ public ListenableFuture<Response> delete();
+
+ @Override
+ public <T> ListenableFuture<T> delete(Class<T> responseType);
+
+ @Override
+ public <T> ListenableFuture<T> delete(GenericType<T> responseType);
+
+ @Override
+ public ListenableFuture<Response> head();
+
+ @Override
+ public ListenableFuture<Response> options();
+
+ @Override
+ public <T> ListenableFuture<T> options(Class<T> responseType);
+
+ @Override
+ public <T> ListenableFuture<T> options(GenericType<T> responseType);
+
+ @Override
+ public ListenableFuture<Response> trace();
+
+ @Override
+ public <T> ListenableFuture<T> trace(Class<T> responseType);
+
+ @Override
+ public <T> ListenableFuture<T> trace(GenericType<T> responseType);
+
+ @Override
+ public ListenableFuture<Response> method(String name);
+
+ @Override
+ public <T> ListenableFuture<T> method(String name, Class<T> responseType);
+
+ @Override
+ public <T> ListenableFuture<T> method(String name, GenericType<T> responseType);
+
+ @Override
+ public ListenableFuture<Response> method(String name, Entity<?> entity);
+
+ @Override
+ public <T> ListenableFuture<T> method(String name, Entity<?> entity, Class<T> responseType);
+
+ @Override
+ public <T> ListenableFuture<T> method(String name, Entity<?> entity, GenericType<T> responseType);
+}
diff --git a/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureInvokerProvider.java b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureInvokerProvider.java
new file mode 100644
index 0000000..ac97dd8
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureInvokerProvider.java
@@ -0,0 +1,42 @@
+/*
+ * 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.client.rx.guava;
+
+import java.util.concurrent.ExecutorService;
+
+import javax.ws.rs.client.RxInvokerProvider;
+import javax.ws.rs.client.SyncInvoker;
+
+/**
+ * Invoker provider for invokers based on Guava's {@code ListenableFuture}.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @since 2.13
+ */
+public final class RxListenableFutureInvokerProvider implements RxInvokerProvider<RxListenableFutureInvoker> {
+
+ @Override
+ public boolean isProviderFor(Class clazz) {
+ return RxListenableFutureInvoker.class.equals(clazz);
+ }
+
+ @Override
+ public JerseyRxListenableFutureInvoker getRxInvoker(SyncInvoker syncInvoker, ExecutorService executorService) {
+ return new JerseyRxListenableFutureInvoker(syncInvoker, executorService);
+ }
+}
diff --git a/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/package-info.java b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/package-info.java
new file mode 100644
index 0000000..4e5eb1c
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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
+ */
+
+/**
+ * Jersey Reactive Client - Guava (ListenableFuture) provider.
+ */
+package org.glassfish.jersey.client.rx.guava;
diff --git a/ext/rx/rx-client-guava/src/main/resources/META-INF/services/org.glassfish.jersey.client.rx.spi.RxInvokerProvider b/ext/rx/rx-client-guava/src/main/resources/META-INF/services/org.glassfish.jersey.client.rx.spi.RxInvokerProvider
new file mode 100644
index 0000000..4246699
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/main/resources/META-INF/services/org.glassfish.jersey.client.rx.spi.RxInvokerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.client.rx.guava.RxListenableFutureInvokerProvider
\ No newline at end of file
diff --git a/ext/rx/rx-client-guava/src/test/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureTest.java b/ext/rx/rx-client-guava/src/test/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureTest.java
new file mode 100644
index 0000000..43a97cf
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/test/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.client.rx.guava;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.core.Is.is;
+
+/**
+ * @author Michal Gajdos
+ */
+public class RxListenableFutureTest {
+
+ private Client client;
+ private ExecutorService executor;
+
+ @Before
+ public void setUp() throws Exception {
+ client = ClientBuilder.newClient().register(TerminalClientRequestFilter.class);
+ executor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder()
+ .setNameFormat("jersey-rx-client-test-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ executor.shutdown();
+ client = null;
+ }
+
+ @Test
+ public void testNotFoundResponse() throws Exception {
+ client.register(RxListenableFutureInvokerProvider.class);
+
+ final RxListenableFutureInvoker invoker = client.target("http://jersey.java.net")
+ .request()
+ .header("Response-Status", 404)
+ .rx(RxListenableFutureInvoker.class);
+
+ testInvoker(invoker, 404, false);
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void testNotFoundReadEntityViaClass() throws Throwable {
+ client.register(RxListenableFutureInvokerProvider.class);
+
+ try {
+ client.target("http://jersey.java.net")
+ .request()
+ .header("Response-Status", 404)
+ .rx(RxListenableFutureInvoker.class)
+ .get(String.class)
+ .get();
+ } catch (final Exception expected) {
+
+ // java.util.concurrent.ExecutionException
+ throw expected
+ // javax.ws.rs.NotFoundException
+ .getCause();
+ }
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void testNotFoundReadEntityViaGenericType() throws Throwable {
+ client.register(RxListenableFutureInvokerProvider.class);
+
+ try {
+ client.target("http://jersey.java.net")
+ .request()
+ .header("Response-Status", 404)
+ .rx(RxListenableFutureInvoker.class)
+ .get(new GenericType<String>() {
+ })
+ .get();
+ } catch (final Exception expected) {
+
+ expected.printStackTrace();
+
+ // java.util.concurrent.ExecutionException
+ throw expected
+ // javax.ws.rs.NotFoundException
+ .getCause();
+ }
+ }
+
+ @Test
+ public void testReadEntityViaClass() throws Throwable {
+ client.register(RxListenableFutureInvokerProvider.class);
+
+ final String response = client.target("http://jersey.java.net")
+ .request()
+ .rx(RxListenableFutureInvoker.class)
+ .get(String.class)
+ .get();
+
+ assertThat(response, is("NO-ENTITY"));
+ }
+
+ @Test
+ public void testReadEntityViaGenericType() throws Throwable {
+ client.register(RxListenableFutureInvokerProvider.class);
+
+ final String response = client.target("http://jersey.java.net")
+ .request()
+ .rx(RxListenableFutureInvoker.class)
+ .get(new GenericType<String>() {
+ })
+ .get();
+
+ assertThat(response, is("NO-ENTITY"));
+ }
+
+ private void testInvoker(final RxListenableFutureInvoker rx,
+ final int expectedStatus,
+ final boolean testDedicatedThread) throws Exception {
+ testResponse(rx.get().get(), expectedStatus, testDedicatedThread);
+ }
+
+ private static void testResponse(final Response response, final int expectedStatus, final boolean testDedicatedThread) {
+ assertThat(response.getStatus(), is(expectedStatus));
+ assertThat(response.readEntity(String.class), is("NO-ENTITY"));
+
+ // Executor.
+ final Matcher<String> matcher = containsString("jersey-rx-client-test");
+ assertThat(response.getHeaderString("Test-Thread"), testDedicatedThread ? matcher : not(matcher));
+
+ // Properties.
+ assertThat(response.getHeaderString("Test-Uri"), is("http://jersey.java.net"));
+ assertThat(response.getHeaderString("Test-Method"), is("GET"));
+ }
+}
diff --git a/ext/rx/rx-client-guava/src/test/java/org/glassfish/jersey/client/rx/guava/TerminalClientRequestFilter.java b/ext/rx/rx-client-guava/src/test/java/org/glassfish/jersey/client/rx/guava/TerminalClientRequestFilter.java
new file mode 100644
index 0000000..b1adaec
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/test/java/org/glassfish/jersey/client/rx/guava/TerminalClientRequestFilter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.client.rx.guava;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.List;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author Michal Gajdos
+ */
+class TerminalClientRequestFilter implements ClientRequestFilter {
+
+ @Override
+ public void filter(final ClientRequestContext requestContext) throws IOException {
+ // Obtain entity - from request or create new.
+ final ByteArrayInputStream entity = new ByteArrayInputStream(
+ requestContext.hasEntity() ? requestContext.getEntity().toString().getBytes() : "NO-ENTITY".getBytes()
+ );
+
+ final int responseStatus = requestContext.getHeaders().getFirst("Response-Status") != null
+ ? (int) requestContext.getHeaders().getFirst("Response-Status") : 200;
+ Response.ResponseBuilder response = Response.status(responseStatus)
+ .entity(entity)
+ .type("application/json")
+ // Test properties.
+ .header("Test-Thread", Thread.currentThread().getName())
+ .header("Test-Uri", requestContext.getUri().toString())
+ .header("Test-Method", requestContext.getMethod());
+
+ // Request headers -> Response headers (<header> -> Test-Header-<header>)
+ for (final MultivaluedMap.Entry<String, List<String>> entry : requestContext.getStringHeaders().entrySet()) {
+ response = response.header("Test-Header-" + entry.getKey(), entry.getValue());
+ }
+
+ // Request properties -> Response headers (<header> -> Test-Property-<header>)
+ for (final String property : requestContext.getPropertyNames()) {
+ response = response.header("Test-Property-" + property, requestContext.getProperty(property));
+ }
+
+ requestContext.abortWith(response.build());
+ }
+}
diff --git a/ext/rx/rx-client-rxjava/pom.xml b/ext/rx/rx-client-rxjava/pom.xml
new file mode 100644
index 0000000..0afec67
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!--
+
+ 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
+
+-->
+
+<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.ext.rx</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-rx-client-rxjava</artifactId>
+ <name>jersey-ext-rx-client-rxjava</name>
+
+ <description>Jersey Reactive Client - RxJava (Observable) provider.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.reactivex</groupId>
+ <artifactId>rxjava</artifactId>
+ <version>${rxjava.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/JerseyRxObservableInvoker.java b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/JerseyRxObservableInvoker.java
new file mode 100644
index 0000000..cfc2b80
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/JerseyRxObservableInvoker.java
@@ -0,0 +1,82 @@
+/*
+ * 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.client.rx.rxjava;
+
+import java.util.concurrent.ExecutorService;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.SyncInvoker;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.client.AbstractRxInvoker;
+
+import rx.Observable;
+import rx.Scheduler;
+import rx.schedulers.Schedulers;
+
+/**
+ * Implementation of Reactive Invoker for {@code Observable}. If no executor service is provided the JAX-RS Async client is used
+ * to retrieve data when a subscriber is subscribed. When an executor service is provided a sync call is invoked on a thread
+ * provided on from this service.
+ *
+ * @author Michal Gajdos
+ * @since 2.13
+ */
+final class JerseyRxObservableInvoker extends AbstractRxInvoker<Observable> implements RxObservableInvoker {
+
+ JerseyRxObservableInvoker(final SyncInvoker syncInvoker, final ExecutorService executor) {
+ super(syncInvoker, executor);
+ }
+
+ @Override
+ public <T> Observable<T> method(final String name, final Entity<?> entity, final Class<T> responseType) {
+ return method(name, entity, new GenericType<T>(responseType) { });
+ }
+
+ @Override
+ public <T> Observable<T> method(final String name, final Entity<?> entity, final GenericType<T> responseType) {
+ final Scheduler scheduler;
+
+ if (getExecutorService() != null) {
+ scheduler = Schedulers.from(getExecutorService());
+ } else {
+ // TODO: use JAX-RS client scheduler
+ // TODO: https://java.net/jira/browse/JAX_RS_SPEC-523
+ scheduler = Schedulers.io();
+ }
+
+ // Invoke as sync JAX-RS client request and subscribe/observe on a scheduler initialized with executor service.
+ return Observable.create((Observable.OnSubscribe<T>) subscriber -> {
+ if (!subscriber.isUnsubscribed()) {
+ try {
+ final T response = getSyncInvoker().method(name, entity, responseType);
+
+ if (!subscriber.isUnsubscribed()) {
+ subscriber.onNext(response);
+ }
+ if (!subscriber.isUnsubscribed()) {
+ subscriber.onCompleted();
+ }
+ } catch (final Throwable throwable) {
+ if (!subscriber.isUnsubscribed()) {
+ subscriber.onError(throwable);
+ }
+ }
+ }
+ }).subscribeOn(scheduler).observeOn(scheduler);
+ }
+}
diff --git a/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/RxObservableInvoker.java b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/RxObservableInvoker.java
new file mode 100644
index 0000000..60d6243
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/RxObservableInvoker.java
@@ -0,0 +1,115 @@
+/*
+ * 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.client.rx.rxjava;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.RxInvoker;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import rx.Observable;
+
+/**
+ * Reactive invoker providing support for {@link rx.Observable observable} from RxJava.
+ * <p/>
+ * Requests are by default invoked on a separate thread (as JAX-RS Async client requests). This behavior can be overridden by
+ * providing a {@link java.util.concurrent.ExecutorService executor service} when client extension is being created
+ * (in {@link org.glassfish.jersey.client.rx.rxjava.RxObservable RxObservable}).
+ * <p/>
+ * The observables produced by method calls are cold observables. That means that request to the service is invoked only when a
+ * subscriber is subscribed to the observable.
+ *
+ * @author Michal Gajdos
+ * @since 2.13
+ */
+public interface RxObservableInvoker extends RxInvoker<Observable> {
+
+ @Override
+ public Observable<Response> get();
+
+ @Override
+ public <T> Observable<T> get(Class<T> responseType);
+
+ @Override
+ public <T> Observable<T> get(GenericType<T> responseType);
+
+ @Override
+ public Observable<Response> put(Entity<?> entity);
+
+ @Override
+ public <T> Observable<T> put(Entity<?> entity, Class<T> clazz);
+
+ @Override
+ public <T> Observable<T> put(Entity<?> entity, GenericType<T> type);
+
+ @Override
+ public Observable<Response> post(Entity<?> entity);
+
+ @Override
+ public <T> Observable<T> post(Entity<?> entity, Class<T> clazz);
+
+ @Override
+ public <T> Observable<T> post(Entity<?> entity, GenericType<T> type);
+
+ @Override
+ public Observable<Response> delete();
+
+ @Override
+ public <T> Observable<T> delete(Class<T> responseType);
+
+ @Override
+ public <T> Observable<T> delete(GenericType<T> responseType);
+
+ @Override
+ public Observable<Response> head();
+
+ @Override
+ public Observable<Response> options();
+
+ @Override
+ public <T> Observable<T> options(Class<T> responseType);
+
+ @Override
+ public <T> Observable<T> options(GenericType<T> responseType);
+
+ @Override
+ public Observable<Response> trace();
+
+ @Override
+ public <T> Observable<T> trace(Class<T> responseType);
+
+ @Override
+ public <T> Observable<T> trace(GenericType<T> responseType);
+
+ @Override
+ public Observable<Response> method(String name);
+
+ @Override
+ public <T> Observable<T> method(String name, Class<T> responseType);
+
+ @Override
+ public <T> Observable<T> method(String name, GenericType<T> responseType);
+
+ @Override
+ public Observable<Response> method(String name, Entity<?> entity);
+
+ @Override
+ public <T> Observable<T> method(String name, Entity<?> entity, Class<T> responseType);
+
+ @Override
+ public <T> Observable<T> method(String name, Entity<?> entity, GenericType<T> responseType);
+}
diff --git a/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/RxObservableInvokerProvider.java b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/RxObservableInvokerProvider.java
new file mode 100644
index 0000000..05591ec
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/RxObservableInvokerProvider.java
@@ -0,0 +1,42 @@
+/*
+ * 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.client.rx.rxjava;
+
+import java.util.concurrent.ExecutorService;
+
+import javax.ws.rs.client.RxInvokerProvider;
+import javax.ws.rs.client.SyncInvoker;
+
+/**
+ * Invoker provider for invokers based on RxJava's {@code Observable}.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @since 2.13
+ */
+public final class RxObservableInvokerProvider implements RxInvokerProvider<RxObservableInvoker> {
+
+ @Override
+ public boolean isProviderFor(Class clazz) {
+ return RxObservableInvoker.class.equals(clazz);
+ }
+
+ @Override
+ public RxObservableInvoker getRxInvoker(SyncInvoker syncInvoker, ExecutorService executorService) {
+ return new JerseyRxObservableInvoker(syncInvoker, executorService);
+ }
+}
diff --git a/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/package-info.java b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/package-info.java
new file mode 100644
index 0000000..1e1c4af
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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
+ */
+
+/**
+ * Jersey Reactive Client - RxJava (Observable) provider.
+ */
+package org.glassfish.jersey.client.rx.rxjava;
diff --git a/ext/rx/rx-client-rxjava/src/main/resources/META-INF/services/org.glassfish.jersey.client.rx.spi.RxInvokerProvider b/ext/rx/rx-client-rxjava/src/main/resources/META-INF/services/org.glassfish.jersey.client.rx.spi.RxInvokerProvider
new file mode 100644
index 0000000..2e85ada
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/main/resources/META-INF/services/org.glassfish.jersey.client.rx.spi.RxInvokerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.client.rx.rxjava.RxObservableInvokerProvider
\ No newline at end of file
diff --git a/ext/rx/rx-client-rxjava/src/test/java/org/glassfish/jersey/client/rx/rxjava/RxObservableTest.java b/ext/rx/rx-client-rxjava/src/test/java/org/glassfish/jersey/client/rx/rxjava/RxObservableTest.java
new file mode 100644
index 0000000..3ca121f
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/test/java/org/glassfish/jersey/client/rx/rxjava/RxObservableTest.java
@@ -0,0 +1,208 @@
+/*
+ * 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.client.rx.rxjava;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.core.Is.is;
+
+import rx.Subscriber;
+
+/**
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class RxObservableTest {
+
+ private Client client;
+ private Client clientWithExecutor;
+ private ExecutorService executor;
+
+ @Before
+ public void setUp() throws Exception {
+ client = ClientBuilder.newClient().register(TerminalClientRequestFilter.class);
+ client.register(RxObservableInvokerProvider.class);
+ executor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder()
+ .setNameFormat("jersey-rx-client-test-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+
+ clientWithExecutor = ClientBuilder.newBuilder().executorService(executor).build();
+ clientWithExecutor.register(TerminalClientRequestFilter.class);
+ clientWithExecutor.register(RxObservableInvokerProvider.class);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ executor.shutdown();
+
+ client.close();
+ client = null;
+ }
+
+ @Test
+ public void testNotFoundResponse() throws Exception {
+ final RxObservableInvoker invoker = client.target("http://jersey.java.net")
+ .request()
+ .header("Response-Status", 404)
+ .rx(RxObservableInvoker.class);
+
+ testInvoker(invoker, 404, false);
+ }
+
+ @Test
+ public void testNotFoundWithCustomExecutor() throws Exception {
+ final RxObservableInvoker invoker = clientWithExecutor.target("http://jersey.java.net")
+ .request()
+ .header("Response-Status", 404)
+ .rx(RxObservableInvoker.class);
+
+ testInvoker(invoker, 404, true);
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void testNotFoundReadEntityViaClass() throws Throwable {
+ try {
+ client.target("http://jersey.java.net")
+ .request()
+ .header("Response-Status", 404)
+ .rx(RxObservableInvoker.class)
+ .get(String.class)
+ .toBlocking()
+ .toFuture()
+ .get();
+ } catch (final Exception expected) {
+ // java.util.concurrent.ExecutionException
+ throw expected
+ // javax.ws.rs.ProcessingException
+ // .getCause()
+ // javax.ws.rs.NotFoundException
+ .getCause();
+ }
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void testNotFoundReadEntityViaGenericType() throws Throwable {
+ try {
+ client.target("http://jersey.java.net")
+ .request()
+ .header("Response-Status", 404)
+ .rx(RxObservableInvoker.class)
+ .get(new GenericType<String>() { })
+ .toBlocking()
+ .toFuture()
+ .get();
+ } catch (final Exception expected) {
+
+ expected.printStackTrace();
+
+ // java.util.concurrent.ExecutionException
+ throw expected
+ // javax.ws.rs.NotFoundException
+ .getCause();
+ }
+ }
+
+ @Test
+ public void testReadEntityViaClass() throws Throwable {
+ final String response = client.target("http://jersey.java.net")
+ .request()
+ .rx(RxObservableInvoker.class)
+ .get(String.class)
+ .toBlocking()
+ .toFuture()
+ .get();
+
+ assertThat(response, is("NO-ENTITY"));
+ }
+
+ @Test
+ public void testReadEntityViaGenericType() throws Throwable {
+ final String response = client.target("http://jersey.java.net")
+ .request()
+ .rx(RxObservableInvoker.class)
+ .get(new GenericType<String>() { })
+ .toBlocking()
+ .toFuture()
+ .get();
+
+ assertThat(response, is("NO-ENTITY"));
+ }
+
+ private void testInvoker(final RxObservableInvoker rx, final int expectedStatus, final boolean testDedicatedThread)
+ throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicReference<Response> responseRef = new AtomicReference<>();
+ final AtomicReference<Throwable> errorRef = new AtomicReference<>();
+
+ rx.get().subscribe(new Subscriber<Response>() {
+ @Override
+ public void onCompleted() {
+ latch.countDown();
+ }
+
+ @Override
+ public void onError(final Throwable e) {
+ errorRef.set(e);
+ latch.countDown();
+ }
+
+ @Override
+ public void onNext(final Response response) {
+ responseRef.set(response);
+ }
+ });
+
+ latch.await();
+
+ if (errorRef.get() == null) {
+ testResponse(responseRef.get(), expectedStatus, testDedicatedThread);
+ } else {
+ throw (Exception) errorRef.get();
+ }
+ }
+
+ private static void testResponse(final Response response, final int expectedStatus, final boolean testDedicatedThread) {
+ assertThat(response.getStatus(), is(expectedStatus));
+ assertThat(response.readEntity(String.class), is("NO-ENTITY"));
+
+ // Executor.
+ assertThat(response.getHeaderString("Test-Thread"), testDedicatedThread
+ ? containsString("jersey-rx-client-test") : containsString("jersey-client-async-executor"));
+
+ // Properties.
+ assertThat(response.getHeaderString("Test-Uri"), is("http://jersey.java.net"));
+ assertThat(response.getHeaderString("Test-Method"), is("GET"));
+ }
+}
diff --git a/ext/rx/rx-client-rxjava/src/test/java/org/glassfish/jersey/client/rx/rxjava/TerminalClientRequestFilter.java b/ext/rx/rx-client-rxjava/src/test/java/org/glassfish/jersey/client/rx/rxjava/TerminalClientRequestFilter.java
new file mode 100644
index 0000000..113961f
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/test/java/org/glassfish/jersey/client/rx/rxjava/TerminalClientRequestFilter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.client.rx.rxjava;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.List;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author Michal Gajdos
+ */
+class TerminalClientRequestFilter implements ClientRequestFilter {
+
+ @Override
+ public void filter(final ClientRequestContext requestContext) throws IOException {
+ // Obtain entity - from request or create new.
+ final ByteArrayInputStream entity = new ByteArrayInputStream(
+ requestContext.hasEntity() ? requestContext.getEntity().toString().getBytes() : "NO-ENTITY".getBytes()
+ );
+
+ final int responseStatus = requestContext.getHeaders().getFirst("Response-Status") != null
+ ? (int) requestContext.getHeaders().getFirst("Response-Status") : 200;
+ Response.ResponseBuilder response = Response.status(responseStatus)
+ .entity(entity)
+ .type("text/plain")
+ // Test properties.
+ .header("Test-Thread", Thread.currentThread().getName())
+ .header("Test-Uri", requestContext.getUri().toString())
+ .header("Test-Method", requestContext.getMethod());
+
+ // Request headers -> Response headers (<header> -> Test-Header-<header>)
+ for (final MultivaluedMap.Entry<String, List<String>> entry : requestContext.getStringHeaders().entrySet()) {
+ response = response.header("Test-Header-" + entry.getKey(), entry.getValue());
+ }
+
+ // Request properties -> Response headers (<header> -> Test-Property-<header>)
+ for (final String property : requestContext.getPropertyNames()) {
+ response = response.header("Test-Property-" + property, requestContext.getProperty(property));
+ }
+
+ requestContext.abortWith(response.build());
+ }
+}
diff --git a/ext/rx/rx-client-rxjava2/pom.xml b/ext/rx/rx-client-rxjava2/pom.xml
new file mode 100644
index 0000000..36614ce
--- /dev/null
+++ b/ext/rx/rx-client-rxjava2/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+
+ 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/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.glassfish.jersey.ext.rx</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-rx-client-rxjava2</artifactId>
+ <name>jersey-ext-rx-client-rxjava2</name>
+
+ <description>Jersey Reactive Client - RxJava2 (Flowable) provider.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.reactivex.rxjava2</groupId>
+ <artifactId>rxjava</artifactId>
+ <version>${rxjava2.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.reactivestreams</groupId>
+ <artifactId>reactive-streams</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/JerseyRxFlowableInvoker.java b/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/JerseyRxFlowableInvoker.java
new file mode 100644
index 0000000..e357319
--- /dev/null
+++ b/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/JerseyRxFlowableInvoker.java
@@ -0,0 +1,80 @@
+/*
+ * 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.client.rx.rxjava2;
+
+import java.util.concurrent.ExecutorService;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.SyncInvoker;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.client.AbstractRxInvoker;
+
+import io.reactivex.BackpressureStrategy;
+import io.reactivex.Flowable;
+import io.reactivex.FlowableEmitter;
+import io.reactivex.FlowableOnSubscribe;
+import io.reactivex.Scheduler;
+import io.reactivex.schedulers.Schedulers;
+
+/**
+ * Implementation of Reactive Invoker for {@code Flowable}. If no executor service is provided the JAX-RS Async client is used
+ * to retrieve data when a subscriber is subscribed. When an executor service is provided a sync call is invoked on a thread
+ * provided on from this service.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @since 2.16
+ */
+final class JerseyRxFlowableInvoker extends AbstractRxInvoker<Flowable> implements RxFlowableInvoker {
+
+ JerseyRxFlowableInvoker(SyncInvoker syncInvoker, ExecutorService executor) {
+ super(syncInvoker, executor);
+ }
+
+ @Override
+ public <T> Flowable<T> method(final String name, final Entity<?> entity, final Class<T> responseType) {
+ return method(name, entity, new GenericType<T>(responseType) { });
+ }
+
+ @Override
+ public <T> Flowable<T> method(final String name, final Entity<?> entity, final GenericType<T> responseType) {
+ final Scheduler scheduler;
+
+ if (getExecutorService() != null) {
+ scheduler = Schedulers.from(getExecutorService());
+ } else {
+ // TODO: use JAX-RS client scheduler
+ // TODO: https://java.net/jira/browse/JAX_RS_SPEC-523
+ scheduler = Schedulers.io();
+ }
+
+ // Invoke as sync JAX-RS client request and subscribe/observe on a scheduler initialized with executor service.
+ return Flowable.create(new FlowableOnSubscribe<T>() {
+ @Override
+ public void subscribe(FlowableEmitter<T> flowableEmitter) throws Exception {
+ try {
+ final T response = getSyncInvoker().method(name, entity, responseType);
+ flowableEmitter.onNext(response);
+ flowableEmitter.onComplete();
+ } catch (final Throwable throwable) {
+ flowableEmitter.onError(throwable);
+ }
+ }
+ }, BackpressureStrategy.DROP).subscribeOn(scheduler).observeOn(scheduler);
+ }
+}
diff --git a/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableInvoker.java b/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableInvoker.java
new file mode 100644
index 0000000..ac9026c
--- /dev/null
+++ b/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableInvoker.java
@@ -0,0 +1,113 @@
+/*
+ * 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.client.rx.rxjava2;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.RxInvoker;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import io.reactivex.Flowable;
+
+
+/**
+ * Reactive invoker providing support for {@link Flowable flowable} from RxJava.
+ * <p/>
+ * Requests are by default invoked on a separate thread (as JAX-RS Async client requests). This behavior can be overridden by
+ * providing a {@link java.util.concurrent.ExecutorService executor service} when client extension is being created.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Michal Gajdos
+ * @since 2.26
+ */
+public interface RxFlowableInvoker extends RxInvoker<Flowable> {
+
+ @Override
+ Flowable<Response> get();
+
+ @Override
+ <R> Flowable<R> get(Class<R> responseType);
+
+ @Override
+ <R> Flowable<R> get(GenericType<R> responseType);
+
+ @Override
+ Flowable<Response> put(Entity<?> entity);
+
+ @Override
+ <R> Flowable<R> put(Entity<?> entity, Class<R> responseType);
+
+ @Override
+ <R> Flowable<R> put(Entity<?> entity, GenericType<R> responseType);
+
+ @Override
+ Flowable<Response> post(Entity<?> entity);
+
+ @Override
+ <R> Flowable<R> post(Entity<?> entity, Class<R> responseType);
+
+ @Override
+ <R> Flowable<R> post(Entity<?> entity, GenericType<R> responseType);
+
+ @Override
+ Flowable<Response> delete();
+
+ @Override
+ <R> Flowable<R> delete(Class<R> responseType);
+
+ @Override
+ <R> Flowable<R> delete(GenericType<R> responseType);
+
+ @Override
+ Flowable<Response> head();
+
+ @Override
+ Flowable<Response> options();
+
+ @Override
+ <R> Flowable<R> options(Class<R> responseType);
+
+ @Override
+ <R> Flowable<R> options(GenericType<R> responseType);
+
+ @Override
+ Flowable<Response> trace();
+
+ @Override
+ <R> Flowable<R> trace(Class<R> responseType);
+
+ @Override
+ <R> Flowable<R> trace(GenericType<R> responseType);
+
+ @Override
+ Flowable<Response> method(String name);
+
+ @Override
+ <R> Flowable<R> method(String name, Class<R> responseType);
+
+ @Override
+ <R> Flowable<R> method(String name, GenericType<R> responseType);
+
+ @Override
+ Flowable<Response> method(String name, Entity<?> entity);
+
+ @Override
+ <R> Flowable<R> method(String name, Entity<?> entity, Class<R> responseType);
+
+ @Override
+ <R> Flowable<R> method(String name, Entity<?> entity, GenericType<R> responseType);
+}
diff --git a/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableInvokerProvider.java b/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableInvokerProvider.java
new file mode 100644
index 0000000..fca6e80
--- /dev/null
+++ b/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableInvokerProvider.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.client.rx.rxjava2;
+
+import java.util.concurrent.ExecutorService;
+
+import javax.ws.rs.client.RxInvokerProvider;
+import javax.ws.rs.client.SyncInvoker;
+
+
+/**
+ * Invoker provider for invokers based on RxJava's {@code Flowable}.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @since 2.26
+ */
+public class RxFlowableInvokerProvider implements RxInvokerProvider<RxFlowableInvoker> {
+
+ @Override
+ public boolean isProviderFor(Class<?> clazz) {
+ return RxFlowableInvoker.class.equals(clazz);
+ }
+
+ @Override
+ public RxFlowableInvoker getRxInvoker(SyncInvoker syncInvoker, ExecutorService executorService) {
+ return new JerseyRxFlowableInvoker(syncInvoker, executorService);
+ }
+}
diff --git a/ext/rx/rx-client-rxjava2/src/test/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableTest.java b/ext/rx/rx-client-rxjava2/src/test/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableTest.java
new file mode 100644
index 0000000..b5a72b0
--- /dev/null
+++ b/ext/rx/rx-client-rxjava2/src/test/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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.client.rx.rxjava2;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.core.Is.is;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class RxFlowableTest {
+
+ private Client client;
+ private Client clientWithExecutor;
+ private ExecutorService executor;
+
+ @Before
+ public void setUp() throws Exception {
+ client = ClientBuilder.newClient().register(TerminalClientRequestFilter.class);
+ client.register(RxFlowableInvokerProvider.class);
+ executor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder()
+ .setNameFormat("jersey-rx-client-test-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .build());
+
+ clientWithExecutor = ClientBuilder.newBuilder().executorService(executor).build();
+ clientWithExecutor.register(TerminalClientRequestFilter.class);
+ clientWithExecutor.register(RxFlowableInvokerProvider.class);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ executor.shutdown();
+
+ client.close();
+ client = null;
+ }
+
+ @Test
+ public void testNotFoundResponse() throws Exception {
+ final RxFlowableInvoker invoker = client.target("http://jersey.java.net")
+ .request()
+ .header("Response-Status", 404)
+ .rx(RxFlowableInvoker.class);
+
+ testInvoker(invoker, 404, false);
+ }
+
+ @Test
+ public void testNotFoundWithCustomExecutor() throws Exception {
+ final RxFlowableInvoker invoker = clientWithExecutor.target("http://jersey.java.net")
+ .request()
+ .header("Response-Status", 404)
+ .rx(RxFlowableInvoker.class);
+
+ testInvoker(invoker, 404, true);
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void testNotFoundReadEntityViaClass() throws Throwable {
+ try {
+ client.target("http://jersey.java.net")
+ .request()
+ .header("Response-Status", 404)
+ .rx(RxFlowableInvoker.class)
+ .get(String.class)
+ .blockingFirst();
+ } catch (final Exception expected) {
+ throw expected;
+ }
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void testNotFoundReadEntityViaGenericType() throws Throwable {
+ try {
+ client.target("http://jersey.java.net")
+ .request()
+ .header("Response-Status", 404)
+ .rx(RxFlowableInvoker.class)
+ .get(new GenericType<String>() {
+ })
+ .blockingFirst();
+ } catch (final Exception expected) {
+ throw expected;
+ }
+ }
+
+ @Test
+ public void testReadEntityViaClass() throws Throwable {
+ final String response = client.target("http://jersey.java.net")
+ .request()
+ .rx(RxFlowableInvoker.class)
+ .get(String.class)
+ .blockingFirst();
+
+ assertThat(response, is("NO-ENTITY"));
+ }
+
+ @Test
+ public void testReadEntityViaGenericType() throws Throwable {
+ final String response = client.target("http://jersey.java.net")
+ .request()
+ .rx(RxFlowableInvoker.class)
+ .get(new GenericType<String>() { })
+ .blockingFirst();
+
+ assertThat(response, is("NO-ENTITY"));
+ }
+
+ private void testInvoker(final RxFlowableInvoker rx,
+ final int expectedStatus,
+ final boolean testDedicatedThread)
+ throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicReference<Response> responseRef = new AtomicReference<>();
+ final AtomicReference<Throwable> errorRef = new AtomicReference<>();
+
+ rx.get().subscribe(new Subscriber<Response>() {
+
+ @Override
+ public void onSubscribe(Subscription s) {
+ s.request(Long.MAX_VALUE);
+ }
+
+ @Override
+ public void onComplete() {
+ latch.countDown();
+ }
+
+ @Override
+ public void onError(final Throwable e) {
+ errorRef.set(e);
+ latch.countDown();
+ }
+
+ @Override
+ public void onNext(final Response response) {
+ responseRef.set(response);
+ }
+ });
+
+ latch.await();
+
+ if (errorRef.get() == null) {
+ testResponse(responseRef.get(), expectedStatus, testDedicatedThread);
+ } else {
+ throw (Exception) errorRef.get();
+ }
+ }
+
+ private static void testResponse(final Response response, final int expectedStatus, final boolean testDedicatedThread) {
+ assertThat(response.getStatus(), is(expectedStatus));
+ assertThat(response.readEntity(String.class), is("NO-ENTITY"));
+
+ // Executor.
+ assertThat(response.getHeaderString("Test-Thread"), testDedicatedThread
+ ? containsString("jersey-rx-client-test") : containsString("jersey-client-async-executor"));
+
+ // Properties.
+ assertThat(response.getHeaderString("Test-Uri"), is("http://jersey.java.net"));
+ assertThat(response.getHeaderString("Test-Method"), is("GET"));
+ }
+}
diff --git a/ext/rx/rx-client-rxjava2/src/test/java/org/glassfish/jersey/client/rx/rxjava2/TerminalClientRequestFilter.java b/ext/rx/rx-client-rxjava2/src/test/java/org/glassfish/jersey/client/rx/rxjava2/TerminalClientRequestFilter.java
new file mode 100644
index 0000000..c724a41
--- /dev/null
+++ b/ext/rx/rx-client-rxjava2/src/test/java/org/glassfish/jersey/client/rx/rxjava2/TerminalClientRequestFilter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.client.rx.rxjava2;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.List;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author Michal Gajdos
+ */
+class TerminalClientRequestFilter implements ClientRequestFilter {
+
+ @Override
+ public void filter(final ClientRequestContext requestContext) throws IOException {
+ // Obtain entity - from request or create new.
+ final ByteArrayInputStream entity = new ByteArrayInputStream(
+ requestContext.hasEntity() ? requestContext.getEntity().toString().getBytes() : "NO-ENTITY".getBytes()
+ );
+
+ final int responseStatus = requestContext.getHeaders().getFirst("Response-Status") != null
+ ? (int) requestContext.getHeaders().getFirst("Response-Status") : 200;
+ Response.ResponseBuilder response = Response.status(responseStatus)
+ .entity(entity)
+ .type("text/plain")
+ // Test properties.
+ .header("Test-Thread", Thread.currentThread().getName())
+ .header("Test-Uri", requestContext.getUri().toString())
+ .header("Test-Method", requestContext.getMethod());
+
+ // Request headers -> Response headers (<header> -> Test-Header-<header>)
+ for (final MultivaluedMap.Entry<String, List<String>> entry : requestContext.getStringHeaders().entrySet()) {
+ response = response.header("Test-Header-" + entry.getKey(), entry.getValue());
+ }
+
+ // Request properties -> Response headers (<header> -> Test-Property-<header>)
+ for (final String property : requestContext.getPropertyNames()) {
+ response = response.header("Test-Property-" + property, requestContext.getProperty(property));
+ }
+
+ requestContext.abortWith(response.build());
+ }
+}
diff --git a/ext/servlet-portability/pom.xml b/ext/servlet-portability/pom.xml
new file mode 100644
index 0000000..6e0fada
--- /dev/null
+++ b/ext/servlet-portability/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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>
+ <artifactId>project</artifactId>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-servlet-portability</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-ext-servlet-portability</name>
+
+ <description>
+ Library that enables writing web applications that run with both Jersey 1.x and Jersey 2.x servlet containers.
+ </description>
+
+ <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>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-servlet</artifactId>
+ <version>${jersey1.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>${servlet2.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/ext/servlet-portability/src/main/java/org/glassfish/jersey/servlet/portability/PortableServletContainer.java b/ext/servlet-portability/src/main/java/org/glassfish/jersey/servlet/portability/PortableServletContainer.java
new file mode 100644
index 0000000..206d494
--- /dev/null
+++ b/ext/servlet-portability/src/main/java/org/glassfish/jersey/servlet/portability/PortableServletContainer.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.servlet.portability;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Jersey Servlet/Filter class that can be referenced in web.xml instead of Jersey 1.x specific
+ * {@code com.sun.jersey.spi.container.servlet.ServletContainer} and Jersey 2.x specific
+ * {@link org.glassfish.jersey.servlet.ServletContainer} to enable web application portability between
+ * Jersey 1.x and Jersey 2.x servlet containers.
+ * <p>
+ * Since for some of the {@link org.glassfish.jersey.servlet.ServletProperties servlet init parameters} that can be
+ * specified in web.xml you may want different values depending on which version of Jersey container is present,
+ * You can prefix the init parameter name either with {@code jersey1#} or {@code jersey2#} to
+ * make it specific to a given version. For example, to specify different values for
+ * {@code javax.ws.rs.Application} init parameter depending on the version of Jersey used, you can include
+ * the following in your web.xml:
+ * <pre>
+ * <servlet>
+ * <servlet-name>Jersey Web Application</servlet-name>
+ * <servlet-class>org.glassfish.jersey.servlet.portability.PortableServletContainer</servlet-class>
+ * <init-param>
+ * <param-name>jersey1#javax.ws.rs.Application</param-name>
+ * <param-value>myapp.jersey1specific.Jersey1Application</param-value>
+ * </init-param>
+ * <init-param>
+ * <param-name>jersey2#javax.ws.rs.Application</param-name>
+ * <param-value>myapp.jersey2specific.Jersey2Application</param-value>
+ * </init-param>
+ * </servlet>
+ * </pre>
+ * </p>
+ *
+ * @author Martin Matula
+ */
+public class PortableServletContainer implements Filter, Servlet {
+
+ private static final String JERSEY_1_PREFIX = "jersey1#";
+ private static final String JERSEY_2_PREFIX = "jersey2#";
+
+ private final Servlet wrappedServlet;
+ private final Filter wrappedFilter;
+ private final String includePrefix;
+ private final String excludePrefix;
+
+ /**
+ * Create a new servlet container.
+ */
+ @SuppressWarnings("unchecked")
+ public PortableServletContainer() {
+ Class<Servlet> servletClass;
+ boolean isJersey1 = false;
+ try {
+ servletClass = (Class<Servlet>) Class.forName("com.sun.jersey.spi.container.servlet.ServletContainer");
+ isJersey1 = true;
+ } catch (ClassNotFoundException e) {
+ // Jersey 1.x not present, try Jersey 2.x
+ try {
+ servletClass = (Class<Servlet>) Class.forName("org.glassfish.jersey.servlet.ServletContainer");
+ } catch (ClassNotFoundException ex) {
+ throw new IllegalStateException(LocalizationMessages.JERSEY_NOT_AVAILABLE());
+ }
+ }
+
+ try {
+ wrappedServlet = servletClass.newInstance();
+ wrappedFilter = (Filter) wrappedServlet;
+ } catch (Exception e) {
+ throw new RuntimeException(LocalizationMessages.JERSEY_CONTAINER_CANT_LOAD(), e);
+ }
+ includePrefix = isJersey1 ? JERSEY_1_PREFIX : JERSEY_2_PREFIX;
+ excludePrefix = isJersey1 ? JERSEY_2_PREFIX : JERSEY_1_PREFIX;
+ }
+
+ @Override
+ public void init(final FilterConfig filterConfig) throws ServletException {
+ wrappedFilter.init(new FilterConfigWrapper(filterConfig));
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ wrappedFilter.doFilter(request, response, chain);
+ }
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ wrappedServlet.init(new ServletConfigWrapper(config));
+ }
+
+ @Override
+ public ServletConfig getServletConfig() {
+ return wrappedServlet.getServletConfig();
+ }
+
+ @Override
+ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
+ wrappedServlet.service(req, res);
+ }
+
+ @Override
+ public String getServletInfo() {
+ return wrappedServlet.getServletInfo();
+ }
+
+ @Override
+ public void destroy() {
+ wrappedServlet.destroy();
+ }
+
+ private abstract class InitParamsWrapper {
+
+ private final HashMap<String, String> filteredInitParams = new HashMap<String, String>();
+
+ void init() {
+ for (Enumeration e = getInitParamNames(); e.hasMoreElements(); ) {
+ String name = (String) e.nextElement();
+ String value = getInitParamValue(name);
+ if (name.startsWith(includePrefix)) {
+ name = name.substring(includePrefix.length());
+ } else if (name.startsWith(excludePrefix)) {
+ continue;
+ }
+ filteredInitParams.put(name, value);
+ }
+ }
+
+ abstract String getInitParamValue(String name);
+
+ abstract Enumeration getInitParamNames();
+
+ public String getInitParameter(String name) {
+ return filteredInitParams.get(name);
+ }
+
+ public Enumeration getInitParameterNames() {
+ return Collections.enumeration(filteredInitParams.keySet());
+ }
+ }
+
+ private class FilterConfigWrapper extends InitParamsWrapper implements FilterConfig {
+
+ private final FilterConfig wrapped;
+
+ FilterConfigWrapper(FilterConfig wrapped) {
+ this.wrapped = wrapped;
+ init();
+ }
+
+ @Override
+ public String getFilterName() {
+ return wrapped.getFilterName();
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return wrapped.getServletContext();
+ }
+
+ @Override
+ String getInitParamValue(String name) {
+ return wrapped.getInitParameter(name);
+ }
+
+ @Override
+ Enumeration getInitParamNames() {
+ return wrapped.getInitParameterNames();
+ }
+ }
+
+ private class ServletConfigWrapper extends InitParamsWrapper implements ServletConfig {
+
+ private final ServletConfig wrapped;
+
+ ServletConfigWrapper(ServletConfig wrapped) {
+ this.wrapped = wrapped;
+ init();
+ }
+
+ @Override
+ String getInitParamValue(String name) {
+ return wrapped.getInitParameter(name);
+ }
+
+ @Override
+ Enumeration getInitParamNames() {
+ return wrapped.getInitParameterNames();
+ }
+
+ @Override
+ public String getServletName() {
+ return wrapped.getServletName();
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return wrapped.getServletContext();
+ }
+ }
+}
diff --git a/ext/servlet-portability/src/main/java/org/glassfish/jersey/servlet/portability/package-info.java b/ext/servlet-portability/src/main/java/org/glassfish/jersey/servlet/portability/package-info.java
new file mode 100644
index 0000000..2629627
--- /dev/null
+++ b/ext/servlet-portability/src/main/java/org/glassfish/jersey/servlet/portability/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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 containing utility classes enabling web applications to be portable between different versions of Jersey
+ * runtime.
+ */
+package org.glassfish.jersey.servlet.portability;
diff --git a/ext/servlet-portability/src/main/resources/org.glassfish.jersey.servlet.portability/localization.properties b/ext/servlet-portability/src/main/resources/org.glassfish.jersey.servlet.portability/localization.properties
new file mode 100644
index 0000000..03f03a4
--- /dev/null
+++ b/ext/servlet-portability/src/main/resources/org.glassfish.jersey.servlet.portability/localization.properties
@@ -0,0 +1,18 @@
+#
+# Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse 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.container.cant.load=Unable to instantiate Jersey ServletContainer.
+jersey.not.available=No Jersey runtime found on the application classpath.
diff --git a/ext/spring3/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java b/ext/spring3/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java
new file mode 100644
index 0000000..6da105f
--- /dev/null
+++ b/ext/spring3/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring.scope;
+
+import javax.ws.rs.container.ContainerRequestContext;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+/**
+ * JAX-RS based Spring RequestAttributes implementation for Servlet-based applications.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+class JaxrsServletRequestAttributes extends ServletRequestAttributes {
+
+ private final ContainerRequestContext requestContext;
+
+ /**
+ * Create a new JAX-RS ServletRequestAttributes instance for the given request.
+ *
+ * @param request current HTTP request
+ * @param requestContext JAX-RS request context
+ */
+ public JaxrsServletRequestAttributes(final HttpServletRequest request, final ContainerRequestContext requestContext) {
+ super(request);
+ this.requestContext = requestContext;
+ }
+
+ @Override
+ public Object resolveReference(String key) {
+ if (REFERENCE_REQUEST.equals(key)) {
+ return this.requestContext;
+ } else if (REFERENCE_SESSION.equals(key)) {
+ return super.getSession(true);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/ext/spring4/pom.xml b/ext/spring4/pom.xml
new file mode 100644
index 0000000..450eba3
--- /dev/null
+++ b/ext/spring4/pom.xml
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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.ext</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>jersey-spring4</artifactId>
+ <name>jersey-spring4</name>
+
+ <packaging>jar</packaging>
+
+ <description>
+ Jersey extension module providing support for Spring 4 integration.
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet-core</artifactId>
+ <version>${project.version}</version>
+ </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>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.2</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.hk2</groupId>
+ <artifactId>hk2</artifactId>
+ <version>${hk2.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.hk2</groupId>
+ <artifactId>spring-bridge</artifactId>
+ <version>${hk2.version}</version>
+ <exclusions>
+ <exclusion> <!-- already pulled in by jersey-server -->
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.glassfish.hk2</groupId>
+ <artifactId>hk2-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-beans</artifactId>
+ <version>${spring4.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-core</artifactId>
+ <version>${spring4.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ <version>${spring4.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-aop</artifactId>
+ <version>${spring4.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>3.0.1</version>
+ <scope>provided</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.aspectj</groupId>
+ <artifactId>aspectjrt</artifactId>
+ <version>1.6.11</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.aspectj</groupId>
+ <artifactId>aspectjweaver</artifactId>
+ <version>1.6.11</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <properties>
+ <spring4.version>4.3.4.RELEASE</spring4.version>
+ </properties>
+
+ <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>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>delayed-strategy-skip-test</id>
+ <activation>
+ <property>
+ <name>org.glassfish.jersey.injection.manager.strategy</name>
+ <value>delayed</value>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skipTests>true</skipTests>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java
new file mode 100644
index 0000000..36b8178
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring;
+
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.Injectee;
+import org.glassfish.jersey.internal.inject.InjectionResolver;
+
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.config.DependencyDescriptor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.MethodParameter;
+
+/**
+ * HK2 injection resolver for Spring framework {@link Autowired} annotation injection.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ * @author Vetle Leinonen-Roeim (vetle at roeim.net)
+ */
+@Singleton
+public class AutowiredInjectResolver implements InjectionResolver<Autowired> {
+
+ private static final Logger LOGGER = Logger.getLogger(AutowiredInjectResolver.class.getName());
+
+ private volatile ApplicationContext ctx;
+
+ /**
+ * Create a new instance.
+ *
+ * @param ctx Spring application context.
+ */
+ public AutowiredInjectResolver(ApplicationContext ctx) {
+ this.ctx = ctx;
+ }
+
+ @Override
+ public Object resolve(Injectee injectee) {
+ AnnotatedElement parent = injectee.getParent();
+ String beanName = null;
+ if (parent != null) {
+ Qualifier an = parent.getAnnotation(Qualifier.class);
+ if (an != null) {
+ beanName = an.value();
+ }
+ }
+ boolean required = parent != null ? parent.getAnnotation(Autowired.class).required() : false;
+ return getBeanFromSpringContext(beanName, injectee, required);
+ }
+
+ private Object getBeanFromSpringContext(String beanName, Injectee injectee, final boolean required) {
+ try {
+ DependencyDescriptor dependencyDescriptor = createSpringDependencyDescriptor(injectee);
+ Set<String> autowiredBeanNames = new HashSet<>(1);
+ autowiredBeanNames.add(beanName);
+ return ctx.getAutowireCapableBeanFactory().resolveDependency(dependencyDescriptor, null,
+ autowiredBeanNames, null);
+ } catch (NoSuchBeanDefinitionException e) {
+ if (required) {
+ LOGGER.warning(e.getMessage());
+ throw e;
+ }
+ return null;
+ }
+ }
+
+ private DependencyDescriptor createSpringDependencyDescriptor(final Injectee injectee) {
+ AnnotatedElement annotatedElement = injectee.getParent();
+
+ if (annotatedElement.getClass().isAssignableFrom(Field.class)) {
+ return new DependencyDescriptor((Field) annotatedElement, !injectee.isOptional());
+ } else if (annotatedElement.getClass().isAssignableFrom(Method.class)) {
+ return new DependencyDescriptor(
+ new MethodParameter((Method) annotatedElement, injectee.getPosition()), !injectee.isOptional());
+ } else {
+ return new DependencyDescriptor(
+ new MethodParameter((Constructor) annotatedElement, injectee.getPosition()), !injectee.isOptional());
+ }
+ }
+
+ @Override
+ public boolean isConstructorParameterIndicator() {
+ return false;
+ }
+
+ @Override
+ public boolean isMethodParameterIndicator() {
+ return false;
+ }
+
+ @Override
+ public Class<Autowired> getAnnotation() {
+ return Autowired.class;
+ }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringComponentProvider.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringComponentProvider.java
new file mode 100644
index 0000000..19f45b3
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringComponentProvider.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2013, 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.spring;
+
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.ServletContext;
+
+import org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager;
+import org.glassfish.jersey.internal.inject.Binding;
+import org.glassfish.jersey.internal.inject.Bindings;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.spi.ComponentProvider;
+
+import org.jvnet.hk2.spring.bridge.api.SpringBridge;
+import org.jvnet.hk2.spring.bridge.api.SpringIntoHK2Bridge;
+
+import org.springframework.aop.framework.Advised;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
+/**
+ * Custom ComponentProvider class.
+ * Responsible for 1) bootstrapping Jersey 2 Spring integration and
+ * 2) making Jersey skip JAX-RS Spring component life-cycle management and leave it to us.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ */
+public class SpringComponentProvider implements ComponentProvider {
+
+ private static final Logger LOGGER = Logger.getLogger(SpringComponentProvider.class.getName());
+ private static final String DEFAULT_CONTEXT_CONFIG_LOCATION = "applicationContext.xml";
+ private static final String PARAM_CONTEXT_CONFIG_LOCATION = "contextConfigLocation";
+ private static final String PARAM_SPRING_CONTEXT = "contextConfig";
+
+ private volatile InjectionManager injectionManager;
+ private volatile ApplicationContext ctx;
+
+ @Override
+ public void initialize(InjectionManager injectionManager) {
+ this.injectionManager = injectionManager;
+
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.fine(LocalizationMessages.CTX_LOOKUP_STARTED());
+ }
+
+ ServletContext sc = injectionManager.getInstance(ServletContext.class);
+
+ if (sc != null) {
+ // servlet container
+ ctx = WebApplicationContextUtils.getWebApplicationContext(sc);
+ } else {
+ // non-servlet container
+ ctx = createSpringContext();
+ }
+ if (ctx == null) {
+ LOGGER.severe(LocalizationMessages.CTX_LOOKUP_FAILED());
+ return;
+ }
+ LOGGER.config(LocalizationMessages.CTX_LOOKUP_SUCESSFUL());
+
+ // initialize HK2 spring-bridge
+
+ ImmediateHk2InjectionManager hk2InjectionManager = (ImmediateHk2InjectionManager) injectionManager;
+ SpringBridge.getSpringBridge().initializeSpringBridge(hk2InjectionManager.getServiceLocator());
+ SpringIntoHK2Bridge springBridge = injectionManager.getInstance(SpringIntoHK2Bridge.class);
+ springBridge.bridgeSpringBeanFactory(ctx);
+
+ injectionManager.register(Bindings.injectionResolver(new AutowiredInjectResolver(ctx)));
+ injectionManager.register(Bindings.service(ctx).to(ApplicationContext.class).named("SpringContext"));
+ LOGGER.config(LocalizationMessages.SPRING_COMPONENT_PROVIDER_INITIALIZED());
+ }
+
+ // detect JAX-RS classes that are also Spring @Components.
+ // register these with HK2 ServiceLocator to manage their lifecycle using Spring.
+ @Override
+ public boolean bind(Class<?> component, Set<Class<?>> providerContracts) {
+
+ if (ctx == null) {
+ return false;
+ }
+
+ if (AnnotationUtils.findAnnotation(component, Component.class) != null) {
+ String[] beanNames = ctx.getBeanNamesForType(component);
+ if (beanNames == null || beanNames.length != 1) {
+ LOGGER.severe(LocalizationMessages.NONE_OR_MULTIPLE_BEANS_AVAILABLE(component));
+ return false;
+ }
+ String beanName = beanNames[0];
+
+ Binding binding = Bindings.supplier(new SpringManagedBeanFactory(ctx, injectionManager, beanName))
+ .to(component)
+ .to(providerContracts);
+ injectionManager.register(binding);
+
+ LOGGER.config(LocalizationMessages.BEAN_REGISTERED(beanNames[0]));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void done() {
+ }
+
+ private ApplicationContext createSpringContext() {
+ ApplicationHandler applicationHandler = injectionManager.getInstance(ApplicationHandler.class);
+ ApplicationContext springContext = (ApplicationContext) applicationHandler.getConfiguration()
+ .getProperty(PARAM_SPRING_CONTEXT);
+ if (springContext == null) {
+ String contextConfigLocation = (String) applicationHandler.getConfiguration()
+ .getProperty(PARAM_CONTEXT_CONFIG_LOCATION);
+ springContext = createXmlSpringConfiguration(contextConfigLocation);
+ }
+ return springContext;
+ }
+
+ private ApplicationContext createXmlSpringConfiguration(String contextConfigLocation) {
+ if (contextConfigLocation == null) {
+ contextConfigLocation = DEFAULT_CONTEXT_CONFIG_LOCATION;
+ }
+ return ctx = new ClassPathXmlApplicationContext(contextConfigLocation, "jersey-spring-applicationContext.xml");
+ }
+
+ private static class SpringManagedBeanFactory implements Supplier {
+
+ private final ApplicationContext ctx;
+ private final InjectionManager injectionManager;
+ private final String beanName;
+
+ private SpringManagedBeanFactory(ApplicationContext ctx, InjectionManager injectionManager, String beanName) {
+ this.ctx = ctx;
+ this.injectionManager = injectionManager;
+ this.beanName = beanName;
+ }
+
+ @Override
+ public Object get() {
+ Object bean = ctx.getBean(beanName);
+ if (bean instanceof Advised) {
+ try {
+ // Unwrap the bean and inject the values inside of it
+ Object localBean = ((Advised) bean).getTargetSource().getTarget();
+ injectionManager.inject(localBean);
+ } catch (Exception e) {
+ // Ignore and let the injection happen as it normally would.
+ injectionManager.inject(bean);
+ }
+ } else {
+ injectionManager.inject(bean);
+ }
+ return bean;
+ }
+ }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringLifecycleListener.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringLifecycleListener.java
new file mode 100644
index 0000000..70a57a5
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringLifecycleListener.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring;
+
+import javax.ws.rs.ext.Provider;
+
+import javax.inject.Inject;
+
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerLifecycleListener;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * JAX-RS Provider class for processing Jersey 2 Spring integration container life-cycle events.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ */
+@Provider
+public class SpringLifecycleListener implements ContainerLifecycleListener {
+
+ @Inject
+ private ApplicationContext ctx;
+
+ @Override
+ public void onStartup(Container container) {
+ }
+
+ @Override
+ public void onReload(Container container) {
+ if (ctx instanceof ConfigurableApplicationContext) {
+ ((ConfigurableApplicationContext) ctx).refresh();
+ }
+ }
+
+ @Override
+ public void onShutdown(Container container) {
+ if (ctx instanceof ConfigurableApplicationContext) {
+ ((ConfigurableApplicationContext) ctx).close();
+ }
+ }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java
new file mode 100644
index 0000000..80a054f
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring;
+
+import java.util.logging.Logger;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.springframework.web.WebApplicationInitializer;
+
+/**
+ * Spring WebApplicationInitializer implementation initializes Spring context by
+ * adding a Spring ContextLoaderListener to the ServletContext.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ */
+public class SpringWebApplicationInitializer implements WebApplicationInitializer {
+
+ private static final Logger LOGGER = Logger.getLogger(SpringWebApplicationInitializer.class.getName());
+
+ private static final String PAR_NAME_CTX_CONFIG_LOCATION = "contextConfigLocation";
+
+ @Override
+ public void onStartup(ServletContext sc) throws ServletException {
+ if (sc.getInitParameter(PAR_NAME_CTX_CONFIG_LOCATION) == null) {
+ LOGGER.config(LocalizationMessages.REGISTERING_CTX_LOADER_LISTENER());
+ sc.setInitParameter(PAR_NAME_CTX_CONFIG_LOCATION, "classpath:applicationContext.xml");
+ sc.addListener("org.springframework.web.context.ContextLoaderListener");
+ sc.addListener("org.springframework.web.context.request.RequestContextListener");
+ } else {
+ LOGGER.config(LocalizationMessages.SKIPPING_CTX_LODAER_LISTENER_REGISTRATION());
+ }
+ }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/package-info.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/package-info.java
new file mode 100644
index 0000000..0176470
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2013, 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 server-side Spring 4 integration classes.
+ *
+ */
+package org.glassfish.jersey.server.spring;
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java
new file mode 100644
index 0000000..c1eb321
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2013, 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.spring.scope;
+
+import javax.ws.rs.container.ContainerRequestContext;
+
+import org.glassfish.jersey.server.spring.LocalizationMessages;
+import org.springframework.util.StringUtils;
+import org.springframework.web.context.request.AbstractRequestAttributes;
+
+/**
+ * JAX-RS based Spring RequestAttributes implementation.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+class JaxrsRequestAttributes extends AbstractRequestAttributes {
+
+ private final ContainerRequestContext requestContext;
+
+ /**
+ * Create a new instance.
+ *
+ * @param requestContext JAX-RS container request context
+ */
+ public JaxrsRequestAttributes(ContainerRequestContext requestContext) {
+ this.requestContext = requestContext;
+ }
+
+ @Override
+ protected void updateAccessedSessionAttributes() {
+ // sessions not supported
+ }
+
+ @Override
+ public Object getAttribute(String name, int scope) {
+ return requestContext.getProperty(name);
+ }
+
+ @Override
+ public void setAttribute(String name, Object value, int scope) {
+ requestContext.setProperty(name, value);
+ }
+
+ @Override
+ public void removeAttribute(String name, int scope) {
+ requestContext.removeProperty(name);
+ }
+
+ @Override
+ public String[] getAttributeNames(int scope) {
+ if (!isRequestActive()) {
+ throw new IllegalStateException(LocalizationMessages.NOT_IN_REQUEST_SCOPE());
+ }
+ return StringUtils.toStringArray(requestContext.getPropertyNames());
+ }
+
+ @Override
+ public void registerDestructionCallback(String name, Runnable callback, int scope) {
+ registerRequestDestructionCallback(name, callback);
+ }
+
+ @Override
+ public Object resolveReference(String key) {
+ if (REFERENCE_REQUEST.equals(key)) {
+ return requestContext;
+ }
+ return null;
+ }
+
+ @Override
+ public String getSessionId() {
+ return null;
+ }
+
+ @Override
+ public Object getSessionMutex() {
+ return null;
+ }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java
new file mode 100644
index 0000000..6da105f
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring.scope;
+
+import javax.ws.rs.container.ContainerRequestContext;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+/**
+ * JAX-RS based Spring RequestAttributes implementation for Servlet-based applications.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+class JaxrsServletRequestAttributes extends ServletRequestAttributes {
+
+ private final ContainerRequestContext requestContext;
+
+ /**
+ * Create a new JAX-RS ServletRequestAttributes instance for the given request.
+ *
+ * @param request current HTTP request
+ * @param requestContext JAX-RS request context
+ */
+ public JaxrsServletRequestAttributes(final HttpServletRequest request, final ContainerRequestContext requestContext) {
+ super(request);
+ this.requestContext = requestContext;
+ }
+
+ @Override
+ public Object resolveReference(String key) {
+ if (REFERENCE_REQUEST.equals(key)) {
+ return this.requestContext;
+ } else if (REFERENCE_SESSION.equals(key)) {
+ return super.getSession(true);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java
new file mode 100644
index 0000000..1cbcbd7
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2013, 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.spring.scope;
+
+import java.io.IOException;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+import javax.ws.rs.container.PreMatching;
+import javax.ws.rs.ext.Provider;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.request.AbstractRequestAttributes;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+
+/**
+ * Spring filter to provide a bridge between JAX-RS and Spring request attributes.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Provider
+@PreMatching
+public final class RequestContextFilter implements ContainerRequestFilter, ContainerResponseFilter {
+
+ private static final String REQUEST_ATTRIBUTES_PROPERTY = RequestContextFilter.class.getName() + ".REQUEST_ATTRIBUTES";
+
+ private final SpringAttributeController attributeController;
+
+ private static final SpringAttributeController EMPTY_ATTRIBUTE_CONTROLLER = new SpringAttributeController() {
+ @Override
+ public void setAttributes(final ContainerRequestContext requestContext) {
+ }
+
+ @Override
+ public void resetAttributes(final ContainerRequestContext requestContext) {
+ }
+ };
+
+ private interface SpringAttributeController {
+
+ void setAttributes(final ContainerRequestContext requestContext);
+
+ void resetAttributes(final ContainerRequestContext requestContext);
+ }
+
+ /**
+ * Create a new request context filter instance.
+ *
+ * @param injectionManager injection manager.
+ */
+ @Inject
+ public RequestContextFilter(final InjectionManager injectionManager) {
+ final ApplicationContext appCtx = injectionManager.getInstance(ApplicationContext.class);
+ final boolean isWebApp = appCtx instanceof WebApplicationContext;
+
+ attributeController = appCtx != null ? new SpringAttributeController() {
+
+ @Override
+ public void setAttributes(final ContainerRequestContext requestContext) {
+ final RequestAttributes attributes;
+ if (isWebApp) {
+ final HttpServletRequest httpRequest = injectionManager.getInstance(HttpServletRequest.class);
+ attributes = new JaxrsServletRequestAttributes(httpRequest, requestContext);
+ } else {
+ attributes = new JaxrsRequestAttributes(requestContext);
+ }
+ requestContext.setProperty(REQUEST_ATTRIBUTES_PROPERTY, attributes);
+ RequestContextHolder.setRequestAttributes(attributes);
+ }
+
+ @Override
+ public void resetAttributes(final ContainerRequestContext requestContext) {
+ final AbstractRequestAttributes attributes =
+ (AbstractRequestAttributes) requestContext.getProperty(REQUEST_ATTRIBUTES_PROPERTY);
+ RequestContextHolder.resetRequestAttributes();
+ attributes.requestCompleted();
+ }
+ } : EMPTY_ATTRIBUTE_CONTROLLER;
+ }
+
+ @Override
+ public void filter(final ContainerRequestContext requestContext) throws IOException {
+ attributeController.setAttributes(requestContext);
+ }
+
+ @Override
+ public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext)
+ throws IOException {
+ attributeController.resetAttributes(requestContext);
+ }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java
new file mode 100644
index 0000000..e15b2c5
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2013, 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 server-side Spring 4 integration injection scopes related classes.
+ *
+ */
+package org.glassfish.jersey.server.spring.scope;
diff --git a/ext/spring4/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider b/ext/spring4/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
new file mode 100644
index 0000000..5aec207
--- /dev/null
+++ b/ext/spring4/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.server.spring.SpringComponentProvider
diff --git a/ext/spring4/src/main/resources/jersey-spring-applicationContext.xml b/ext/spring4/src/main/resources/jersey-spring-applicationContext.xml
new file mode 100644
index 0000000..00e4dda
--- /dev/null
+++ b/ext/spring4/src/main/resources/jersey-spring-applicationContext.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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
+
+-->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+ <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
+ <property name="scopes">
+ <map>
+ <entry key="request">
+ <bean class="org.springframework.web.context.request.RequestScope"/>
+ </entry>
+ </map>
+ </property>
+ </bean>
+</beans>
diff --git a/ext/spring4/src/main/resources/org/glassfish/jersey/server/spring/localization.properties b/ext/spring4/src/main/resources/org/glassfish/jersey/server/spring/localization.properties
new file mode 100644
index 0000000..6fd0a06
--- /dev/null
+++ b/ext/spring4/src/main/resources/org/glassfish/jersey/server/spring/localization.properties
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2013, 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
+#
+
+no.beans.found.for.type=No beans found. Resolution failed for type {0}.
+ctx.lookup.started=Spring context lookup started.
+ctx.lookup.failed=Spring context lookup failed, skipping spring component provider initialization.
+ctx.lookup.sucessful=Spring context lookup done.
+spring.component.provider.initialized=Spring component provider initialized.
+none.or.multiple.beans.available=None or multiple beans found in Spring context for type {0}, skipping the type.
+bean.registered=Spring managed bean, {0}, registered with HK2.
+registering.ctx.loader.listener=Registering Spring ContextLoaderListener
+skipping.ctx.lodaer.listener.registration=Presuming Spring ContextLoaderListener was manually registered. Skipping context loader registration.
+not.in.request.scope=Cannot ask for request attributes - request is not active anymore!
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/NoComponent.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/NoComponent.java
new file mode 100644
index 0000000..111361c
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/NoComponent.java
@@ -0,0 +1,21 @@
+/*
+ * 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.server.spring;
+
+public class NoComponent {
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/SpringTestConfiguration.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/SpringTestConfiguration.java
new file mode 100644
index 0000000..5a8123c
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/SpringTestConfiguration.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan(basePackages = "org.glassfish.jersey.server.spring")
+public class SpringTestConfiguration {
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent1.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent1.java
new file mode 100644
index 0000000..7a065c1
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent1.java
@@ -0,0 +1,27 @@
+/*
+ * 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.server.spring;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class TestComponent1 {
+
+ public String result() {
+ return "test ok";
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2.java
new file mode 100644
index 0000000..792d9b8
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2.java
@@ -0,0 +1,21 @@
+/*
+ * 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.server.spring;
+
+public interface TestComponent2 {
+ String result();
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl1.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl1.java
new file mode 100644
index 0000000..c8c61d0
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl1.java
@@ -0,0 +1,27 @@
+/*
+ * 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.server.spring;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class TestComponent2Impl1 implements TestComponent2 {
+ @Override
+ public String result() {
+ return "test ok";
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl2.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl2.java
new file mode 100644
index 0000000..9900cd4
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl2.java
@@ -0,0 +1,27 @@
+/*
+ * 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.server.spring;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class TestComponent2Impl2 implements TestComponent2 {
+ @Override
+ public String result() {
+ return "test ok";
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4JTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4JTest.java
new file mode 100644
index 0000000..6b64034
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4JTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.server.spring.aspect4j;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import static org.junit.Assert.assertEquals;
+
+public class Aspect4JTest extends JerseyTest {
+
+ private ClassPathXmlApplicationContext applicationContext;
+
+ private TestAspect testAspect;
+
+ @Before
+ public void before() {
+ testAspect.reset();
+ }
+
+ @Override
+ protected Application configure() {
+ applicationContext = new ClassPathXmlApplicationContext("jersey-spring-aspect4j-applicationContext.xml");
+ testAspect = applicationContext.getBean(TestAspect.class);
+ return new Aspect4jJerseyConfig()
+ .property("contextConfig", applicationContext);
+ }
+
+ @Test
+ public void methodCallShouldNotBeIntercepted() {
+ target("test1").request().get(String.class);
+ assertEquals(0, testAspect.getInterceptions());
+ }
+
+ @Test
+ public void methodCallShouldBeIntercepted() {
+ target("test2").request().get(String.class);
+ assertEquals(1, applicationContext.getBean(TestAspect.class).getInterceptions());
+ }
+
+ @Test
+ public void JERSEY_3126() {
+ final String result = target("JERSEY-3126").request().get(String.class);
+ assertEquals("test ok", result);
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4jJerseyConfig.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4jJerseyConfig.java
new file mode 100644
index 0000000..4506c7e
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4jJerseyConfig.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring.aspect4j;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+
+public class Aspect4jJerseyConfig extends ResourceConfig {
+
+ public Aspect4jJerseyConfig() {
+ register(RequestContextFilter.class);
+ register(NoComponentResource.class);
+ register(ComponentResource.class);
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/ComponentResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/ComponentResource.java
new file mode 100644
index 0000000..b9b3596
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/ComponentResource.java
@@ -0,0 +1,49 @@
+/*
+ * 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.server.spring.aspect4j;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+import org.glassfish.jersey.server.spring.TestComponent1;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Path("/")
+@Component
+public class ComponentResource {
+
+ @Autowired
+ private TestComponent1 testComponent1;
+ @Context
+ private UriInfo uriInfo;
+
+ @Path("test2")
+ @GET
+ public String test2() {
+ return testComponent1.result();
+ }
+
+ @Path("JERSEY-3126")
+ @GET
+ public String JERSEY_3126() {
+ return uriInfo == null ? "test failed" : "test ok";
+ }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/NoComponentResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/NoComponentResource.java
new file mode 100644
index 0000000..eaa3643
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/NoComponentResource.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring.aspect4j;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+import org.glassfish.jersey.server.spring.TestComponent1;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Path("/")
+public class NoComponentResource {
+
+ @Autowired
+ private TestComponent1 testComponent1;
+
+ @Path("test1")
+ @GET
+ public String test1() {
+ return testComponent1.result();
+ }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/TestAspect.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/TestAspect.java
new file mode 100644
index 0000000..3a80614
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/TestAspect.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring.aspect4j;
+
+import javax.inject.Singleton;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+
+@Aspect
+@Singleton
+public class TestAspect {
+
+ private int interceptions = 0;
+
+ @Before("execution(* org.glassfish.jersey.server.spring.aspect4j.*.*())")
+ public void intercept(JoinPoint joinPoint) {
+ interceptions++;
+ }
+
+ public int getInterceptions() {
+ return interceptions;
+ }
+
+ public void reset() {
+ interceptions = 0;
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionJerseyTestConfig.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionJerseyTestConfig.java
new file mode 100644
index 0000000..df30805
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionJerseyTestConfig.java
@@ -0,0 +1,27 @@
+/*
+ * 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.server.spring.fieldinjection;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+
+public class SpringFieldInjectionJerseyTestConfig extends ResourceConfig {
+ public SpringFieldInjectionJerseyTestConfig() {
+ register(RequestContextFilter.class);
+ register(SpringFieldInjectionTestResource.class);
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTest.java
new file mode 100644
index 0000000..9ec3a54
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring.fieldinjection;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spring.SpringTestConfiguration;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import static org.junit.Assert.assertEquals;
+
+public class SpringFieldInjectionTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ ApplicationContext context = new AnnotationConfigApplicationContext(SpringTestConfiguration.class);
+ return new SpringFieldInjectionJerseyTestConfig()
+ .property("contextConfig", context);
+ }
+
+ @Test
+ public void testInjectionOfSingleBean() {
+ String result = target("test1").request().get(String.class);
+ assertEquals("test ok", result);
+ }
+
+ @Test
+ public void testInjectionOfListOfBeans() {
+ String result = target("test2").request().get(String.class);
+ assertEquals("test ok", result);
+ }
+
+ @Test
+ public void testInjectionOfSetOfBeans() {
+ String result = target("test3").request().get(String.class);
+ assertEquals("test ok", result);
+ }
+
+ @Test
+ public void JERSEY_2643() {
+ String result = target("JERSEY-2643").request().get(String.class);
+ assertEquals("test ok", result);
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTestResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTestResource.java
new file mode 100644
index 0000000..16e6de1
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTestResource.java
@@ -0,0 +1,72 @@
+/*
+ * 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.server.spring.fieldinjection;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+import org.glassfish.jersey.server.spring.NoComponent;
+import org.glassfish.jersey.server.spring.TestComponent1;
+import org.glassfish.jersey.server.spring.TestComponent2;
+import org.springframework.beans.factory.annotation.Autowired;
+
+@Path("/")
+public class SpringFieldInjectionTestResource {
+
+ @Autowired
+ private TestComponent1 testComponent1;
+
+ @Autowired
+ private List<TestComponent2> testComponent2List;
+
+ @Autowired
+ Set<TestComponent2> testComponent2Set;
+
+ @Autowired(required = false)
+ private NoComponent noComponent;
+
+ @Path("test1")
+ @GET
+ public String test1() {
+ return testComponent1.result();
+ }
+
+ @Path("test2")
+ @GET
+ public String test2() {
+ return (testComponent2List.size() == 2 && "test ok".equals(testComponent2List.get(0).result())
+ && "test ok".equals(testComponent2List.get(1).result())) ? "test ok" : "test failed";
+ }
+
+ @Path("test3")
+ @GET
+ public String test3() {
+ Iterator<TestComponent2> iterator = testComponent2Set.iterator();
+ return (testComponent2Set.size() == 2 && "test ok".equals(iterator.next().result())
+ && "test ok".equals(iterator.next().result())) ? "test ok" : "test failed";
+ }
+
+ @Path("JERSEY-2643")
+ @GET
+ public String JERSEY_2643() {
+ return noComponent == null ? "test ok" : "test failed";
+ }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/Counter.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/Counter.java
new file mode 100644
index 0000000..28de237
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/Counter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring.filter;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class Counter {
+
+ private int count = 0;
+
+ public void inc() {
+ count++;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/FilterTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/FilterTest.java
new file mode 100644
index 0000000..539b633
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/FilterTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.server.spring.filter;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spring.SpringTestConfiguration;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import static org.junit.Assert.assertEquals;
+
+public class FilterTest extends JerseyTest {
+
+ private ApplicationContext context;
+
+ @Override
+ protected Application configure() {
+ context = new AnnotationConfigApplicationContext(SpringTestConfiguration.class);
+ return new JerseyTestConfig()
+ .property("contextConfig", context);
+ }
+
+ @Test
+ public void testInjectionOfSingleBean() {
+ target("test1").request().get(String.class);
+ assertEquals(1, context.getBean(Counter.class).getCount());
+ }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/JerseyTestConfig.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/JerseyTestConfig.java
new file mode 100644
index 0000000..9fb32a8
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/JerseyTestConfig.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring.filter;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+
+public class JerseyTestConfig extends ResourceConfig {
+
+ public JerseyTestConfig() {
+ register(RequestContextFilter.class);
+ register(TestResource.class);
+ register(TestFilter.class);
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/TestFilter.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/TestFilter.java
new file mode 100644
index 0000000..dbc4afb
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/TestFilter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring.filter;
+
+import java.io.IOException;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+
+import org.springframework.stereotype.Component;
+
+@Component
+@Singleton
+public class TestFilter implements ContainerRequestFilter {
+
+ @Inject
+ private Counter counter;
+
+ @Override
+ public void filter(final ContainerRequestContext requestContext) throws IOException {
+ counter.inc();
+ }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/TestResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/TestResource.java
new file mode 100644
index 0000000..4401629
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/TestResource.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring.filter;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+@Path("/")
+public class TestResource {
+
+ @Path("test1")
+ @GET
+ public String test1() {
+ return "Hello, Test!";
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionJerseyTestConfig.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionJerseyTestConfig.java
new file mode 100644
index 0000000..5a9d3b7
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionJerseyTestConfig.java
@@ -0,0 +1,27 @@
+/*
+ * 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.server.spring.methodinjection;
+
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+import org.glassfish.jersey.server.ResourceConfig;
+
+public class SpringMethodInjectionJerseyTestConfig extends ResourceConfig {
+ public SpringMethodInjectionJerseyTestConfig() {
+ register(RequestContextFilter.class);
+ register(SpringMethodInjectionTestResource.class);
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTest.java
new file mode 100644
index 0000000..5ce4fec
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.server.spring.methodinjection;
+
+import static org.junit.Assert.assertEquals;
+
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.glassfish.jersey.server.spring.SpringTestConfiguration;
+
+import javax.ws.rs.core.Application;
+
+public class SpringMethodInjectionTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ ApplicationContext context = new AnnotationConfigApplicationContext(SpringTestConfiguration.class);
+ return new SpringMethodInjectionJerseyTestConfig()
+ .property("contextConfig", context);
+ }
+
+ @Test
+ public void testInjectionOfSingleBean() {
+ String result = target("test1").request().get(String.class);
+ assertEquals("test ok", result);
+ }
+
+ @Test
+ public void testInjectionOfListOfBeans() {
+ String result = target("test2").request().get(String.class);
+ assertEquals("test ok", result);
+ }
+
+ @Test
+ public void testInjectionOfSetOfBeans() {
+ String result = target("test3").request().get(String.class);
+ assertEquals("test ok", result);
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTestResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTestResource.java
new file mode 100644
index 0000000..0cb3d68
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTestResource.java
@@ -0,0 +1,83 @@
+/*
+ * 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.server.spring.methodinjection;
+
+import org.glassfish.jersey.server.spring.NoComponent;
+import org.glassfish.jersey.server.spring.TestComponent1;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.List;
+import java.util.Set;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+@Path("/")
+public class SpringMethodInjectionTestResource {
+
+ private TestComponent1 testComponent1;
+ private List<org.glassfish.jersey.server.spring.TestComponent2> testComponent2List;
+ private Set<org.glassfish.jersey.server.spring.TestComponent2> testComponent2Set;
+ private NoComponent noComponent;
+
+ @Autowired
+ public void setTestComponent1(TestComponent1 testComponent1) {
+ this.testComponent1 = testComponent1;
+ }
+
+ @Autowired
+ public void setTestComponent2List(List<org.glassfish.jersey.server.spring.TestComponent2> testComponent2List) {
+ this.testComponent2List = testComponent2List;
+ }
+
+ @Autowired
+ public void setTestComponent2Set(Set<org.glassfish.jersey.server.spring.TestComponent2> testComponent2Set) {
+ this.testComponent2Set = testComponent2Set;
+ }
+
+ @Autowired(required = false)
+ public void setNoComponent(NoComponent noComponent) {
+ this.noComponent = noComponent;
+ }
+
+ @Path("test1")
+ @GET
+ public String test1() {
+ return testComponent1.result();
+ }
+
+ @Path("test2")
+ @GET
+ public String test2() {
+ return (testComponent2List.size() == 2 && "test ok".equals(testComponent2List.get(0).result())
+ && "test ok".equals(testComponent2List.get(1).result())) ? "test ok" : "test failed";
+ }
+
+ @Path("test3")
+ @GET
+ public String test3() {
+ java.util.Iterator<org.glassfish.jersey.server.spring.TestComponent2> iterator = testComponent2Set.iterator();
+ return (testComponent2Set.size() == 2 && "test ok".equals(iterator.next().result())
+ && "test ok".equals(iterator.next().result())) ? "test ok" : "test failed";
+ }
+
+ @Path("JERSEY-2643")
+ @GET
+ public String JERSEY_2643() {
+ return noComponent == null ? "test ok" : "test failed";
+ }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionJerseyTestConfig.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionJerseyTestConfig.java
new file mode 100644
index 0000000..d67ebd4
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionJerseyTestConfig.java
@@ -0,0 +1,27 @@
+/*
+ * 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.server.spring.parameterinjection;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+
+public class SpringParameterInjectionJerseyTestConfig extends ResourceConfig {
+ public SpringParameterInjectionJerseyTestConfig() {
+ register(RequestContextFilter.class);
+ register(SpringParameterInjectionTestResource.class);
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTest.java
new file mode 100644
index 0000000..35caa98
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.server.spring.parameterinjection;
+
+import org.glassfish.jersey.server.spring.SpringTestConfiguration;
+import org.glassfish.jersey.server.spring.fieldinjection.SpringFieldInjectionJerseyTestConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import javax.ws.rs.core.Application;
+
+import static org.junit.Assert.assertEquals;
+
+public class SpringParameterInjectionTest extends JerseyTest {
+ @Override
+ protected Application configure() {
+ ApplicationContext context = new AnnotationConfigApplicationContext(SpringTestConfiguration.class);
+ return new SpringParameterInjectionJerseyTestConfig()
+ .property("contextConfig", context);
+ }
+
+ @Test
+ public void testInjectionOfSingleBean() {
+ String result = target("test1").request().get(String.class);
+ assertEquals("test ok", result);
+ }
+
+ @Test
+ public void testInjectionOfListOfBeans() {
+ String result = target("test2").request().get(String.class);
+ assertEquals("test ok", result);
+ }
+
+ @Test
+ public void testInjectionOfSetOfBeans() {
+ String result = target("test3").request().get(String.class);
+ assertEquals("test ok", result);
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTestResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTestResource.java
new file mode 100644
index 0000000..aefb0d1
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTestResource.java
@@ -0,0 +1,65 @@
+/*
+ * 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.server.spring.parameterinjection;
+
+import org.glassfish.jersey.server.spring.TestComponent1;
+import org.glassfish.jersey.server.spring.TestComponent2;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+@Path("/")
+public class SpringParameterInjectionTestResource {
+
+ private final TestComponent1 testComponent1;
+ private final List<TestComponent2> testComponent2List;
+ private final Set<TestComponent2> testComponent2Set;
+
+ @Autowired
+ public SpringParameterInjectionTestResource(final TestComponent1 testComponent1,
+ final List<TestComponent2> testComponent2List,
+ final Set<TestComponent2> testComponent2Set) {
+ this.testComponent1 = testComponent1;
+ this.testComponent2List = testComponent2List;
+ this.testComponent2Set = testComponent2Set;
+ }
+
+ @Path("test1")
+ @GET
+ public String test1() {
+ return testComponent1.result();
+ }
+
+ @Path("test2")
+ @GET
+ public String test2() {
+ return (testComponent2List.size() == 2 && "test ok".equals(testComponent2List.get(0).result())
+ && "test ok".equals(testComponent2List.get(1).result())) ? "test ok" : "test failed";
+ }
+
+ @Path("test3")
+ @GET
+ public String test3() {
+ Iterator<TestComponent2> iterator = testComponent2Set.iterator();
+ return (testComponent2Set.size() == 2 && "test ok".equals(iterator.next().result())
+ && "test ok".equals(iterator.next().result())) ? "test ok" : "test failed";
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DefaultTestService.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DefaultTestService.java
new file mode 100644
index 0000000..040d053
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DefaultTestService.java
@@ -0,0 +1,28 @@
+/*
+ * 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.server.spring.profiles;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class DefaultTestService implements TestService {
+
+ @Override
+ public String test() {
+ return "default";
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DevTestService.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DevTestService.java
new file mode 100644
index 0000000..dc3c995
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DevTestService.java
@@ -0,0 +1,32 @@
+/*
+ * 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.server.spring.profiles;
+
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+@Component
+@Primary
+@Profile("dev")
+public class DevTestService implements TestService {
+
+ @Override
+ public String test() {
+ return "dev";
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDefaultProfileResourceTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDefaultProfileResourceTest.java
new file mode 100644
index 0000000..f390c5c
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDefaultProfileResourceTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.server.spring.profiles;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+public class SpringDefaultProfileResourceTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ System.setProperty("spring.profiles.active", "");
+ ApplicationContext context = new AnnotationConfigApplicationContext("org.glassfish.jersey.server.spring.profiles");
+ return new ResourceConfig()
+ .register(RequestContextFilter.class)
+ .register(LoggingFeature.class)
+ .packages("org.glassfish.jersey.server.spring.profiles")
+ .property("contextConfig", context);
+ }
+
+ @Test
+ public void shouldUseDefaultComponent() {
+ final String result = target("spring-resource").request().get(String.class);
+ Assert.assertEquals("default", result);
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDevProfileResourceTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDevProfileResourceTest.java
new file mode 100644
index 0000000..6c8450c
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDevProfileResourceTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.server.spring.profiles;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+public class SpringDevProfileResourceTest extends JerseyTest {
+
+ @Override
+ protected Application configure() {
+ System.setProperty("spring.profiles.active", "dev");
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ "org.glassfish.jersey.server.spring.profiles");
+ return new ResourceConfig()
+ .register(RequestContextFilter.class)
+ .register(LoggingFeature.class)
+ .packages("org.glassfish.jersey.server.spring.profiles")
+ .property("contextConfig", context);
+ }
+
+ @Test
+ public void shouldUseDevProfileBean() {
+ final String result = target("spring-resource").request().get(String.class);
+ Assert.assertEquals("dev", result);
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringProfilesTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringProfilesTest.java
new file mode 100644
index 0000000..8bccbe3
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringProfilesTest.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.server.spring.profiles;
+
+import org.junit.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import static org.junit.Assert.assertEquals;
+
+public class SpringProfilesTest {
+
+ @Test
+ public void shouldGetDefaultBean() {
+ System.setProperty("spring.profiles.active", "");
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ "org.glassfish.jersey.server.spring.profiles");
+ assertEquals("default", context.getBean(TestService.class).test());
+ }
+
+ @Test
+ public void shouldGetDevProfileBean() {
+ System.setProperty("spring.profiles.active", "dev");
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ "org.glassfish.jersey.server.spring.profiles");
+ assertEquals("dev", context.getBean(TestService.class).test());
+ }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringRequestResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringRequestResource.java
new file mode 100644
index 0000000..11f8bf7
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringRequestResource.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring.profiles;
+
+import javax.inject.Singleton;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+
+@Singleton
+@Path("spring-resource")
+@Service
+public class SpringRequestResource {
+
+ @Autowired
+ private TestService testService;
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getGoodbye() {
+ return testService.test();
+ }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/TestService.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/TestService.java
new file mode 100644
index 0000000..045d3e2
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/TestService.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse 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.spring.profiles;
+
+public interface TestService {
+
+ String test();
+}
diff --git a/ext/spring4/src/test/resources/jersey-spring-aspect4j-applicationContext.xml b/ext/spring4/src/test/resources/jersey-spring-aspect4j-applicationContext.xml
new file mode 100644
index 0000000..c80c155
--- /dev/null
+++ b/ext/spring4/src/test/resources/jersey-spring-aspect4j-applicationContext.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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
+
+-->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/aop
+ http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
+
+ <aop:aspectj-autoproxy/>
+
+ <context:component-scan base-package="org.glassfish.jersey.server.spring"/>
+
+ <bean id="someAspect" class="org.glassfish.jersey.server.spring.aspect4j.TestAspect"/>
+</beans>
diff --git a/ext/wadl-doclet/pom.xml b/ext/wadl-doclet/pom.xml
new file mode 100644
index 0000000..c90da71
--- /dev/null
+++ b/ext/wadl-doclet/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse 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">
+ <parent>
+ <artifactId>project</artifactId>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>jersey-wadl-doclet</artifactId>
+ <packaging>jar</packaging>
+ <name>jersey-wadl-doclet</name>
+
+ <description>A doclet that generates a resourcedoc xml file: this file contains the javadoc documentation
+ of resource classes, so that this can be used for extending generated wadl with useful
+ documentation.
+ </description>
+
+
+ <profiles>
+ <profile>
+ <id>jdk1.7</id>
+ <activation>
+ <jdk>1.7</jdk>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>com.sun</groupId>
+ <artifactId>tools</artifactId>
+ <version>1.7.0</version>
+ <scope>system</scope>
+ <systemPath>${java.home}/../lib/tools.jar</systemPath>
+ </dependency>
+ </dependencies>
+ </profile>
+ <profile>
+ <id>jdk1.8</id>
+ <activation>
+ <jdk>1.8</jdk>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>com.sun</groupId>
+ <artifactId>tools</artifactId>
+ <version>1.8.0</version>
+ <scope>system</scope>
+ <systemPath>${java.home}/../lib/tools.jar</systemPath>
+ </dependency>
+ </dependencies>
+ </profile>
+ <profile>
+ <id>tools.jar</id>
+ <activation>
+ <!-- Activation should be done with <file>${java.home}/../lib/tools.jar</file But this is not working with maven as the property is not expansed. -->
+ <property>
+ <name>java.vendor</name>
+ <value>Sun Microsystems Inc.</value>
+ </property>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>com.sun</groupId>
+ <artifactId>tools</artifactId>
+ <version>1.6</version>
+ <scope>system</scope>
+ <systemPath>${java.home}/../lib/tools.jar</systemPath>
+ </dependency>
+ </dependencies>
+ </profile>
+ </profiles>
+
+ <dependencies>
+ <dependency>
+ <groupId>xerces</groupId>
+ <artifactId>xercesImpl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/DocProcessor.java b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/DocProcessor.java
new file mode 100644
index 0000000..61d832e
--- /dev/null
+++ b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/DocProcessor.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2010, 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.wadl.doclet;
+
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ClassDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.MethodDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ParamDocType;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.ParamTag;
+import com.sun.javadoc.Parameter;
+
+/**
+ * A doc processor is handed over javadoc elements so that it can turn this into
+ * resource doc elements, even self defined.
+ *
+ * @author Martin Grotzke (martin.grotzke at freiheit.com)
+ */
+public interface DocProcessor {
+
+ /**
+ * Specify jaxb classes of instances that you add to the {@code resourcedoc} model.
+ * These classes are added to the list of classes when creating the jaxb context
+ * with {@code JAXBContext.newInstance( clazzes );}.
+ *
+ * @return a list of classes or {@code null}
+ */
+ Class<?>[] getRequiredJaxbContextClasses();
+
+ /**
+ * specify which of your elements you want to be handled as CDATA.
+ * The use of the '^' between the {@code namespaceURI} and the {@code localname}
+ * seems to be an implementation detail of the xerces code.
+ * When processing xml that doesn't use namespaces, simply omit the
+ * namespace prefix as shown in the third CDataElement below.
+ *
+ * @return an Array of element descriptors or {@code null}
+ */
+ String[] getCDataElements();
+
+ /**
+ * Use this method to extend the provided {@link ClassDocType} with the information from
+ * the given {@link ClassDoc}.
+ *
+ * @param classDoc the class javadoc
+ * @param classDocType the {@link ClassDocType} to extend. This will later be processed by the
+ * {@link org.glassfish.jersey.server.wadl.WadlGenerator}s.
+ */
+ void processClassDoc(ClassDoc classDoc, ClassDocType classDocType);
+
+ /**
+ * Process the provided methodDoc and add your custom information to the methodDocType.<br>
+ * Use e.g. {@link MethodDocType#getAny()} to store custom elements.
+ *
+ * @param methodDoc the {@link MethodDoc} representing the docs of your method.
+ * @param methodDocType the related {@link MethodDocType} that will later be processed by the
+ * {@link org.glassfish.jersey.server.wadl.WadlGenerator}s.
+ */
+ void processMethodDoc(MethodDoc methodDoc, MethodDocType methodDocType);
+
+ /**
+ * Use this method to extend the provided {@link ParamDocType} with the information from the
+ * given {@link ParamTag} and {@link Parameter}.
+ *
+ * @param paramTag the parameter javadoc
+ * @param parameter the parameter (that is documented or not)
+ * @param paramDocType the {@link ParamDocType} to extend. This will later be processed by the
+ * {@link org.glassfish.jersey.server.wadl.WadlGenerator}s.
+ */
+ void processParamTag(ParamTag paramTag, Parameter parameter, ParamDocType paramDocType);
+
+}
diff --git a/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/DocProcessorWrapper.java b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/DocProcessorWrapper.java
new file mode 100644
index 0000000..8dec3b5
--- /dev/null
+++ b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/DocProcessorWrapper.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2010, 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.wadl.doclet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ClassDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.MethodDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ParamDocType;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.ParamTag;
+import com.sun.javadoc.Parameter;
+
+/**
+ * This {@link DocProcessor} wraps multiple {@code DocProcessor}s.
+ *
+ * @author Martin Grotzke (martin.grotzke at freiheit.com)
+ */
+public class DocProcessorWrapper implements DocProcessor {
+
+ private final List<DocProcessor> _docProcessors;
+
+ /**
+ * Create new {@code DocProcessorWrapper} instance.
+ */
+ public DocProcessorWrapper() {
+ _docProcessors = new ArrayList<>();
+ }
+
+ /**
+ * Add a new {@code DocProcessor} instance to the list of wrapped instances.
+ *
+ * @param docProcessor {@code DocProcessor} instance to wrap.
+ */
+ void add(DocProcessor docProcessor) {
+ _docProcessors.add(docProcessor);
+ }
+
+ @Override
+ public Class<?>[] getRequiredJaxbContextClasses() {
+ final List<Class<?>> result = new ArrayList<>();
+ for (DocProcessor docProcessor : _docProcessors) {
+ final Class<?>[] requiredJaxbContextClasses = docProcessor.getRequiredJaxbContextClasses();
+ if (requiredJaxbContextClasses != null && requiredJaxbContextClasses.length > 0) {
+ result.addAll(Arrays.asList(requiredJaxbContextClasses));
+ }
+ }
+ return result.toArray(new Class<?>[result.size()]);
+ }
+
+ @Override
+ public String[] getCDataElements() {
+ final List<String> result = new ArrayList<>();
+ for (DocProcessor docProcessor : _docProcessors) {
+ final String[] cdataElements = docProcessor.getCDataElements();
+ if (cdataElements != null && cdataElements.length > 0) {
+ result.addAll(Arrays.asList(cdataElements));
+ }
+ }
+ return result.toArray(new String[result.size()]);
+ }
+
+ @Override
+ public void processClassDoc(ClassDoc classDoc, ClassDocType classDocType) {
+ for (DocProcessor docProcessor : _docProcessors) {
+ docProcessor.processClassDoc(classDoc, classDocType);
+ }
+ }
+
+ @Override
+ public void processMethodDoc(MethodDoc methodDoc,
+ MethodDocType methodDocType) {
+ for (DocProcessor docProcessor : _docProcessors) {
+ docProcessor.processMethodDoc(methodDoc, methodDocType);
+ }
+ }
+
+ @Override
+ public void processParamTag(ParamTag paramTag, Parameter parameter,
+ ParamDocType paramDocType) {
+ for (DocProcessor docProcessor : _docProcessors) {
+ docProcessor.processParamTag(paramTag, parameter, paramDocType);
+ }
+ }
+
+}
diff --git a/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/ResourceDoclet.java b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/ResourceDoclet.java
new file mode 100644
index 0000000..a6b472d
--- /dev/null
+++ b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/ResourceDoclet.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright (c) 2010, 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.wadl.doclet;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Marshaller;
+import javax.xml.namespace.QName;
+
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.AnnotationDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ClassDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.MethodDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.NamedValueType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ParamDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.RepresentationDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.RequestDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ResourceDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ResponseDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.WadlParamType;
+
+import org.apache.xml.serialize.OutputFormat;
+import org.apache.xml.serialize.XMLSerializer;
+
+import com.sun.javadoc.AnnotationDesc;
+import com.sun.javadoc.AnnotationDesc.ElementValuePair;
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.DocErrorReporter;
+import com.sun.javadoc.MemberDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.ParamTag;
+import com.sun.javadoc.Parameter;
+import com.sun.javadoc.RootDoc;
+import com.sun.javadoc.SeeTag;
+import com.sun.javadoc.Tag;
+
+/**
+ * Creates a resourcedoc XML file.
+ * <p/>
+ * <p>
+ * The ResourceDoc file contains the javadoc documentation
+ * of resource classes, so that this can be used for extending generated wadl with useful
+ * documentation.
+ * </p>
+ *
+ * @author <a href="mailto:martin.grotzke@freiheit.com">Martin Grotzke</a>
+ */
+public class ResourceDoclet {
+
+ private static final Pattern PATTERN_RESPONSE_REPRESENTATION = Pattern.compile("@response\\.representation\\.([\\d]+)\\..*");
+ private static final String OPTION_OUTPUT = "-output";
+ private static final String OPTION_CLASSPATH = "-classpath";
+ private static final String OPTION_DOC_PROCESSORS = "-processors";
+
+ private static final Logger LOG = Logger.getLogger(ResourceDoclet.class.getName());
+
+ /**
+ * Start the doclet.
+ *
+ * @param root the root JavaDoc document.
+ * @return true if no exception is thrown.
+ */
+ public static boolean start(final RootDoc root) {
+ final String output = getOptionArg(root.options(), OPTION_OUTPUT);
+
+ final String classpath = getOptionArg(root.options(), OPTION_CLASSPATH);
+ // LOG.info( "Have classpath: " + classpath );
+ final String[] classpathElements = classpath.split(File.pathSeparator);
+
+ final ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ final ClassLoader ncl = new Loader(classpathElements,
+ ResourceDoclet.class.getClassLoader());
+ Thread.currentThread().setContextClassLoader(ncl);
+
+ final String docProcessorOption = getOptionArg(root.options(), OPTION_DOC_PROCESSORS);
+ final String[] docProcessors = docProcessorOption != null ? docProcessorOption.split(":") : null;
+ final DocProcessorWrapper docProcessor = new DocProcessorWrapper();
+ try {
+ if (docProcessors != null && docProcessors.length > 0) {
+ final Class<?> clazz = Class.forName(docProcessors[0], true, Thread.currentThread().getContextClassLoader());
+ final Class<? extends DocProcessor> dpClazz = clazz.asSubclass(DocProcessor.class);
+ docProcessor.add(dpClazz.newInstance());
+ }
+ } catch (final Exception e) {
+ LOG.log(Level.SEVERE, "Could not load docProcessors " + docProcessorOption, e);
+ }
+
+ try {
+ final ResourceDocType result = new ResourceDocType();
+
+ final ClassDoc[] classes = root.classes();
+ for (final ClassDoc classDoc : classes) {
+ LOG.fine("Writing class " + classDoc.qualifiedTypeName());
+ final ClassDocType classDocType = new ClassDocType();
+ classDocType.setClassName(classDoc.qualifiedTypeName());
+ classDocType.setCommentText(classDoc.commentText());
+ docProcessor.processClassDoc(classDoc, classDocType);
+
+ for (final MethodDoc methodDoc : classDoc.methods()) {
+
+ final MethodDocType methodDocType = new MethodDocType();
+ methodDocType.setMethodName(methodDoc.name());
+ methodDocType.setMethodSignature(methodDoc.signature());
+ methodDocType.setCommentText(methodDoc.commentText());
+ docProcessor.processMethodDoc(methodDoc, methodDocType);
+
+ addParamDocs(methodDoc, methodDocType, docProcessor);
+
+ addRequestRepresentationDoc(methodDoc, methodDocType);
+
+ addResponseDoc(methodDoc, methodDocType);
+
+ classDocType.getMethodDocs().add(methodDocType);
+ }
+
+ result.getDocs().add(classDocType);
+ }
+
+ try {
+ final Class<?>[] clazzes = getJAXBContextClasses(result, docProcessor);
+ final JAXBContext c = JAXBContext.newInstance(clazzes);
+ final Marshaller m = c.createMarshaller();
+ m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+ final OutputStream out = new BufferedOutputStream(new FileOutputStream(output));
+
+ final String[] cdataElements = getCDataElements(docProcessor);
+ final XMLSerializer serializer = getXMLSerializer(out, cdataElements);
+
+ m.marshal(result, serializer);
+ out.close();
+
+ LOG.info("Wrote " + output);
+
+ } catch (final Exception e) {
+ LOG.log(Level.SEVERE, "Could not serialize ResourceDoc.", e);
+ return false;
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(cl);
+ }
+
+ return true;
+ }
+
+ private static String[] getCDataElements(final DocProcessor docProcessor) {
+ final String[] original = new String[] {"ns1^commentText", "ns2^commentText", "^commentText"};
+ if (docProcessor == null) {
+ return original;
+ } else {
+ final String[] cdataElements = docProcessor.getCDataElements();
+ if (cdataElements == null || cdataElements.length == 0) {
+ return original;
+ } else {
+
+ final String[] result = copyOf(original, original.length + cdataElements.length);
+ for (int i = 0; i < cdataElements.length; i++) {
+ result[original.length + i] = cdataElements[i];
+ }
+ return result;
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T, U> T[] copyOf(final U[] original, final int newLength) {
+ final T[] copy = (original.getClass() == Object[].class)
+ ? (T[]) new Object[newLength]
+ : (T[]) Array.newInstance(original.getClass().getComponentType(), newLength);
+ System.arraycopy(original, 0, copy, 0,
+ Math.min(original.length, newLength));
+ return copy;
+ }
+
+ private static Class<?>[] getJAXBContextClasses(
+ final ResourceDocType result, final DocProcessor docProcessor) {
+ final Class<?>[] clazzes;
+ if (docProcessor == null) {
+ clazzes = new Class<?>[1];
+ } else {
+ final Class<?>[] requiredJaxbContextClasses = docProcessor.getRequiredJaxbContextClasses();
+ if (requiredJaxbContextClasses != null) {
+ clazzes = new Class<?>[1 + requiredJaxbContextClasses.length];
+ for (int i = 0; i < requiredJaxbContextClasses.length; i++) {
+ clazzes[i + 1] = requiredJaxbContextClasses[i];
+ }
+ } else {
+ clazzes = new Class<?>[1];
+ }
+ }
+ clazzes[0] = result.getClass();
+ return clazzes;
+ }
+
+ private static XMLSerializer getXMLSerializer(final OutputStream os, final String[] cdataElements)
+ throws InstantiationException,
+ IllegalAccessException, ClassNotFoundException {
+ // configure an OutputFormat to handle CDATA
+ final OutputFormat of = new OutputFormat();
+
+ // specify which of your elements you want to be handled as CDATA.
+ // The use of the '^' between the namespaceURI and the localname
+ // seems to be an implementation detail of the xerces code.
+ // When processing xml that doesn't use namespaces, simply omit the
+ // namespace prefix as shown in the third CDataElement below.
+ of.setCDataElements(cdataElements);
+
+ // set any other options you'd like
+ of.setPreserveSpace(true);
+ of.setIndenting(true);
+
+ // create the serializer
+ final XMLSerializer serializer = new XMLSerializer(of);
+
+ serializer.setOutputByteStream(os);
+
+ return serializer;
+ }
+
+ private static void addResponseDoc(final MethodDoc methodDoc,
+ final MethodDocType methodDocType) {
+
+ final ResponseDocType responseDoc = new ResponseDocType();
+
+ final Tag returnTag = getSingleTagOrNull(methodDoc, "return");
+ if (returnTag != null) {
+ responseDoc.setReturnDoc(returnTag.text());
+ }
+
+ final Tag[] responseParamTags = methodDoc.tags("response.param");
+ for (final Tag responseParamTag : responseParamTags) {
+ // LOG.info( "Have responseparam tag: " + print( responseParamTag ) );
+ final WadlParamType wadlParam = new WadlParamType();
+ for (final Tag inlineTag : responseParamTag.inlineTags()) {
+ final String tagName = inlineTag.name();
+ final String tagText = inlineTag.text();
+ /* skip empty tags
+ */
+ if (isEmpty(tagText)) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Skipping empty inline tag of @response.param in method "
+ + methodDoc.qualifiedName() + ": " + tagName);
+ }
+ continue;
+ }
+ switch (tagName) {
+ case "@name":
+ wadlParam.setName(tagText);
+ break;
+ case "@style":
+ wadlParam.setStyle(tagText);
+ break;
+ case "@type":
+ wadlParam.setType(QName.valueOf(tagText));
+ break;
+ case "@doc":
+ wadlParam.setDoc(tagText);
+ break;
+ default:
+ LOG.warning("Unknown inline tag of @response.param in method "
+ + methodDoc.qualifiedName() + ": " + tagName
+ + " (value: " + tagText + ")");
+ break;
+ }
+ }
+ responseDoc.getWadlParams().add(wadlParam);
+ }
+
+ final Map<String, List<Tag>> tagsByStatus = getResponseRepresentationTags(methodDoc);
+ for (final Entry<String, List<Tag>> entry : tagsByStatus.entrySet()) {
+ final RepresentationDocType representationDoc = new RepresentationDocType();
+ representationDoc.setStatus(Long.valueOf(entry.getKey()));
+ for (final Tag tag : entry.getValue()) {
+ if (tag.name().endsWith(".qname")) {
+ representationDoc.setElement(QName.valueOf(tag.text()));
+ } else if (tag.name().endsWith(".mediaType")) {
+ representationDoc.setMediaType(tag.text());
+ } else if (tag.name().endsWith(".example")) {
+ representationDoc.setExample(getSerializedExample(tag));
+ } else if (tag.name().endsWith(".doc")) {
+ representationDoc.setDoc(tag.text());
+ } else {
+ LOG.warning("Unknown response representation tag " + tag.name());
+ }
+ }
+ responseDoc.getRepresentations().add(representationDoc);
+ }
+
+ methodDocType.setResponseDoc(responseDoc);
+ }
+
+ private static boolean isEmpty(final String value) {
+ return value == null || value.isEmpty() || value.trim().isEmpty();
+ }
+
+ private static void addRequestRepresentationDoc(final MethodDoc methodDoc,
+ final MethodDocType methodDocType) {
+ final Tag requestElement = getSingleTagOrNull(methodDoc, "request.representation.qname");
+ final Tag requestExample = getSingleTagOrNull(methodDoc, "request.representation.example");
+ if (requestElement != null || requestExample != null) {
+ final RequestDocType requestDoc = new RequestDocType();
+ final RepresentationDocType representationDoc = new RepresentationDocType();
+
+ /* requestElement exists
+ */
+ if (requestElement != null) {
+ representationDoc.setElement(QName.valueOf(requestElement.text()));
+ }
+
+ /* requestExample exists
+ */
+ if (requestExample != null) {
+ final String example = getSerializedExample(requestExample);
+ if (!isEmpty(example)) {
+ representationDoc.setExample(example);
+ } else {
+ LOG.warning("Could not get serialized example for method " + methodDoc.qualifiedName());
+ }
+ }
+
+ requestDoc.setRepresentationDoc(representationDoc);
+ methodDocType.setRequestDoc(requestDoc);
+ }
+ }
+
+ private static Map<String, List<Tag>> getResponseRepresentationTags(final MethodDoc methodDoc) {
+ final Map<String, List<Tag>> tagsByStatus = new HashMap<>();
+ for (final Tag tag : methodDoc.tags()) {
+ final Matcher matcher = PATTERN_RESPONSE_REPRESENTATION.matcher(tag.name());
+ if (matcher.matches()) {
+ final String status = matcher.group(1);
+ List<Tag> tags = tagsByStatus.get(status);
+ if (tags == null) {
+ tags = new ArrayList<>();
+ tagsByStatus.put(status, tags);
+ }
+ tags.add(tag);
+ }
+ }
+ return tagsByStatus;
+ }
+
+ /**
+ * Searches an <code>@link</code> tag within the inline tags of the specified tag
+ * and serializes the referenced instance.
+ *
+ * @param tag the tag containing the inline tags to be searched.
+ * @return the {@code String} representation of the {@link com.sun.javadoc.Tag} or null if the parameter is null.
+ */
+ private static String getSerializedExample(final Tag tag) {
+ if (tag != null) {
+ final Tag[] inlineTags = tag.inlineTags();
+ if (inlineTags != null && inlineTags.length > 0) {
+ for (final Tag inlineTag : inlineTags) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Have inline tag: " + print(inlineTag));
+ }
+ if ("@link".equals(inlineTag.name())) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Have link: " + print(inlineTag));
+ }
+ final SeeTag linkTag = (SeeTag) inlineTag;
+ return getSerializedLinkFromTag(linkTag);
+ } else if (!isEmpty(inlineTag.text())) {
+ return inlineTag.text();
+ }
+ }
+ } else {
+ LOG.fine("Have example: " + print(tag));
+ return tag.text();
+ }
+ }
+ return null;
+ }
+
+ private static Tag getSingleTagOrNull(final MethodDoc methodDoc, final String tagName) {
+ final Tag[] tags = methodDoc.tags(tagName);
+ if (tags != null && tags.length == 1) {
+ return tags[0];
+ }
+ return null;
+ }
+
+ private static void addParamDocs(final MethodDoc methodDoc,
+ final MethodDocType methodDocType,
+ final DocProcessor docProcessor) {
+ final Parameter[] parameters = methodDoc.parameters();
+ final ParamTag[] paramTags = methodDoc.paramTags();
+
+ /* only use both javadoc and reflection information when the number
+ * of params are the same
+ */
+ if (parameters != null && paramTags != null
+ && parameters.length == paramTags.length) {
+
+ for (int i = 0; i < parameters.length; i++) {
+ final Parameter parameter = parameters[i];
+
+ /* TODO: this only works if the params and tags are in the same
+ * order. If the param tags are mixed up, the comments for parameters
+ * will be wrong.
+ */
+ final ParamTag paramTag = paramTags[i];
+
+ final ParamDocType paramDocType = new ParamDocType();
+ paramDocType.setParamName(paramTag.parameterName());
+ paramDocType.setCommentText(paramTag.parameterComment());
+ docProcessor.processParamTag(paramTag, parameter, paramDocType);
+
+ final AnnotationDesc[] annotations = parameter.annotations();
+ if (annotations != null) {
+ for (final AnnotationDesc annotationDesc : annotations) {
+ final AnnotationDocType annotationDocType = new AnnotationDocType();
+ final String typeName = annotationDesc.annotationType().qualifiedName();
+ annotationDocType.setAnnotationTypeName(typeName);
+ for (final ElementValuePair elementValuePair : annotationDesc.elementValues()) {
+ final NamedValueType namedValueType = new NamedValueType();
+ namedValueType.setName(elementValuePair.element().name());
+ namedValueType.setValue(elementValuePair.value().value().toString());
+ annotationDocType.getAttributeDocs().add(namedValueType);
+ }
+ paramDocType.getAnnotationDocs().add(annotationDocType);
+ }
+ }
+
+ methodDocType.getParamDocs().add(paramDocType);
+
+ }
+
+ }
+ }
+
+ private static String getSerializedLinkFromTag(final SeeTag linkTag) {
+ final MemberDoc referencedMember = linkTag.referencedMember();
+
+ if (referencedMember == null) {
+ throw new NullPointerException("Referenced member of @link " + print(linkTag) + " cannot be resolved.");
+ }
+
+ if (!referencedMember.isStatic()) {
+ LOG.warning("Referenced member of @link " + print(linkTag) + " is not static."
+ + " Right now only references to static members are supported.");
+ return null;
+ }
+
+ /* Get referenced example bean
+ */
+ final ClassDoc containingClass = referencedMember.containingClass();
+ final Object object;
+ try {
+ final Field declaredField = Class.forName(containingClass.qualifiedName(), false, Thread.currentThread()
+ .getContextClassLoader()).getDeclaredField(referencedMember.name());
+ if (referencedMember.isFinal()) {
+ declaredField.setAccessible(true);
+ }
+ object = declaredField.get(null);
+ LOG.log(Level.FINE, "Got object " + object);
+ } catch (final Exception e) {
+ LOG.info("Have classloader: " + ResourceDoclet.class.getClassLoader().getClass());
+ LOG.info("Have thread classloader " + Thread.currentThread().getContextClassLoader().getClass());
+ LOG.info("Have system classloader " + ClassLoader.getSystemClassLoader().getClass());
+ LOG.log(Level.SEVERE, "Could not get field " + referencedMember.qualifiedName(), e);
+ return null;
+ }
+
+ /* marshal the bean to xml
+ */
+ try {
+ final JAXBContext jaxbContext = JAXBContext.newInstance(object.getClass());
+ final StringWriter stringWriter = new StringWriter();
+ final Marshaller marshaller = jaxbContext.createMarshaller();
+ marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+ marshaller.marshal(object, stringWriter);
+ final String result = stringWriter.getBuffer().toString();
+ LOG.log(Level.FINE, "Got marshalled output:\n" + result);
+ return result;
+ } catch (final Exception e) {
+ LOG.log(Level.SEVERE, "Could serialize bean to xml: " + object, e);
+ return null;
+ }
+ }
+
+ private static String print(final Tag tag) {
+ return String.valueOf(tag.getClass()) + "["
+ + "firstSentenceTags=" + toCSV(tag.firstSentenceTags())
+ + ", inlineTags=" + toCSV(tag.inlineTags())
+ + ", kind=" + tag.kind()
+ + ", name=" + tag.name()
+ + ", text=" + tag.text()
+ + "]";
+ }
+
+ static String toCSV(final Tag[] items) {
+ if (items == null) {
+ return null;
+ }
+ return toCSV(Arrays.asList(items));
+ }
+
+ static String toCSV(final Collection<Tag> items) {
+ return toCSV(items, ", ", null);
+ }
+
+ static String toCSV(final Collection<Tag> items, final String separator, final String delimiter) {
+ if (items == null) {
+ return null;
+ }
+ if (items.isEmpty()) {
+ return "";
+ }
+ final StringBuilder sb = new StringBuilder();
+ for (final Iterator<Tag> iter = items.iterator(); iter.hasNext(); ) {
+ if (delimiter != null) {
+ sb.append(delimiter);
+ }
+ final Tag item = iter.next();
+ sb.append(item.name());
+ if (delimiter != null) {
+ sb.append(delimiter);
+ }
+ if (iter.hasNext()) {
+ sb.append(separator);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Return array length for given option: 1 + the number of arguments that
+ * the option takes.
+ *
+ * @param option option
+ * @return the number of args for the specified option
+ */
+ public static int optionLength(final String option) {
+ LOG.fine("Invoked with option " + option);
+
+ if (OPTION_OUTPUT.equals(option)
+ || OPTION_CLASSPATH.equals(option)
+ || OPTION_DOC_PROCESSORS.equals(option)) {
+ return 2;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Validate options.
+ *
+ * @param options options to be validated
+ * @param reporter {@link com.sun.javadoc.DocErrorReporter} for collecting eventual errors
+ * @return if the specified options are valid
+ */
+ public static boolean validOptions(final String[][] options, final DocErrorReporter reporter) {
+ return validOption(OPTION_OUTPUT, "<path-to-file>", options, reporter)
+ && validOption(OPTION_CLASSPATH, "<path>", options, reporter);
+ }
+
+ private static boolean validOption(final String optionName,
+ final String reportOptionName,
+ final String[][] options,
+ final DocErrorReporter reporter) {
+ final String option = getOptionArg(options, optionName);
+
+ final boolean foundOption = option != null && !option.trim().isEmpty();
+ if (!foundOption) {
+ reporter.printError(optionName + " " + reportOptionName + " must be specified.");
+ }
+ return foundOption;
+ }
+
+ private static String getOptionArg(final String[][] options, final String option) {
+
+ for (final String[] opt : options) {
+ if (opt[0].equals(option)) {
+ return opt[1];
+ }
+ }
+
+ return null;
+ }
+
+ static class Loader extends URLClassLoader {
+
+ public Loader(final String[] paths, final ClassLoader parent) {
+ super(getURLs(paths), parent);
+ }
+
+ Loader(final String[] paths) {
+ super(getURLs(paths));
+ }
+
+ private static URL[] getURLs(final String[] paths) {
+ final List<URL> urls = new ArrayList<>();
+ for (final String path : paths) {
+ try {
+ urls.add(new File(path).toURI().toURL());
+ } catch (final MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return urls.toArray(new URL[urls.size()]);
+ }
+
+ }
+
+}
diff --git a/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/package-info.java b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/package-info.java
new file mode 100644
index 0000000..324b8fc
--- /dev/null
+++ b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2013, 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 classes used for doclet generation for WADL purposes. Classes are used during the compile time
+ * when doclet is generated. Later the result is used for WADL generation.
+ *
+ */
+package org.glassfish.jersey.wadl.doclet;