/**
* Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.seedstack.seed.rest.jersey2.internal;
import com.google.common.collect.Lists;
import io.nuun.kernel.api.plugin.InitState;
import io.nuun.kernel.api.plugin.context.InitContext;
import io.nuun.kernel.core.AbstractPlugin;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.server.mvc.jsp.JspMvcFeature;
import org.glassfish.jersey.servlet.ServletProperties;
import org.seedstack.seed.rest.RestConfig;
import org.seedstack.seed.rest.internal.RestPlugin;
import org.seedstack.seed.rest.spi.RestProvider;
import org.seedstack.seed.web.spi.FilterDefinition;
import org.seedstack.seed.web.spi.ListenerDefinition;
import org.seedstack.seed.web.spi.ServletDefinition;
import org.seedstack.seed.web.spi.WebProvider;
import org.seedstack.shed.reflect.Classes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.ConstrainedTo;
import javax.ws.rs.RuntimeType;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Jersey2Plugin extends AbstractPlugin implements WebProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(Jersey2Plugin.class);
private FilterDefinition jersey2filterDefinition;
private SeedServletContainer seedServletContainer;
@Override
public String name() {
return "jersey2";
}
@Override
public Collection<Class<?>> requiredPlugins() {
return Lists.newArrayList(RestPlugin.class, RestProvider.class);
}
@Override
public InitState init(InitContext initContext) {
RestPlugin restPlugin = initContext.dependency(RestPlugin.class);
if (restPlugin.isEnabled()) {
RestConfig restConfig = restPlugin.getRestConfig();
Set<Class<?>> resources = new HashSet<>();
Set<Class<?>> providers = new HashSet<>();
List<RestProvider> restProviders = initContext.dependencies(RestProvider.class);
for (RestProvider restProvider : restProviders) {
resources.addAll(restProvider.resources());
providers.addAll(filterClasses(restProvider.providers()));
}
LOGGER.debug("Detected {} JAX-RS resource(s)", resources.size());
LOGGER.debug("Detected {} JAX-RS provider(s)", providers.size());
Set<Class<?>> enabledFeatures = new HashSet<>();
if (isMultipartFeaturePresent()) {
enabledFeatures.add(MultiPartFeature.class);
LOGGER.trace("Detected and enabled JAX-RS multipart feature");
}
if (isJspFeaturePresent()) {
enabledFeatures.add(JspMvcFeature.class);
LOGGER.trace("Detected and enabled JAX-RS JSP feature");
}
for (Class<?> featureClass : filterClasses(restConfig.getFeatures())) {
enabledFeatures.add(featureClass);
LOGGER.trace("Enabled JAX-RS feature " + featureClass.getCanonicalName());
}
LOGGER.debug("Enabled {} JAX-RS feature(s)", enabledFeatures.size());
Map<String, Object> jersey2Properties = buildJerseyProperties(restConfig);
jersey2filterDefinition = new FilterDefinition("jersey2", SeedServletContainer.class);
jersey2filterDefinition.setAsyncSupported(true);
jersey2filterDefinition.addMappings(new FilterDefinition.Mapping(restConfig.getPath() + "/*"));
jersey2filterDefinition.addInitParameters(buildInitParams(jersey2Properties));
seedServletContainer = new SeedServletContainer(resources, providers, enabledFeatures, jersey2Properties);
LOGGER.info("Jersey 2 serving JAX-RS resources on {}/*", restConfig.getPath());
}
return InitState.INITIALIZED;
}
private Set<Class<?>> filterClasses(Collection<Class<?>> classes) {
Set<Class<?>> result = new HashSet<>();
if (classes != null) {
for (Class<?> aClass : classes) {
ConstrainedTo annotation = aClass.getAnnotation(ConstrainedTo.class);
if (annotation == null || annotation.value() == RuntimeType.SERVER) {
result.add(aClass);
}
}
}
return result;
}
private Map<String, Object> buildJerseyProperties(RestConfig restConfig) {
Map<String, Object> jerseyProperties = new HashMap<>();
// Default configuration values
jerseyProperties.put(ServletProperties.FILTER_FORWARD_ON_404, true);
jerseyProperties.put(ServerProperties.WADL_FEATURE_DISABLE, true);
// User-defined configuration values
jerseyProperties.putAll(restConfig.getJerseyProperties());
// Forced configuration values
jerseyProperties.put(ServletProperties.FILTER_CONTEXT_PATH, restConfig.getPath());
if (isJspFeaturePresent()) {
jerseyProperties.put(JspMvcFeature.TEMPLATE_BASE_PATH, restConfig.getJspPath());
}
return jerseyProperties;
}
private Map<String, String> buildInitParams(Map<String, Object> jerseyProperties) {
Map<String, String> initParams = new HashMap<>();
// Those properties must be defined as init parameters of the filter
if (jerseyProperties.containsKey(ServletProperties.FILTER_CONTEXT_PATH)) {
initParams.put(ServletProperties.FILTER_CONTEXT_PATH, (String) jerseyProperties.get(ServletProperties.FILTER_CONTEXT_PATH));
}
if (jerseyProperties.containsKey(ServletProperties.FILTER_STATIC_CONTENT_REGEX)) {
initParams.put(ServletProperties.FILTER_STATIC_CONTENT_REGEX, (String) jerseyProperties.get(ServletProperties.FILTER_STATIC_CONTENT_REGEX));
}
return initParams;
}
private boolean isJspFeaturePresent() {
return Classes.optional("org.glassfish.jersey.server.mvc.jsp.JspMvcFeature").isPresent();
}
private boolean isMultipartFeaturePresent() {
return Classes.optional("org.glassfish.jersey.media.multipart.MultiPartFeature").isPresent();
}
@Override
public Object nativeUnitModule() {
return seedServletContainer != null ? new Jersey2Module(seedServletContainer) : null;
}
@Override
public List<ServletDefinition> servlets() {
return null;
}
@Override
public List<FilterDefinition> filters() {
return jersey2filterDefinition != null ? Lists.newArrayList(jersey2filterDefinition) : null;
}
@Override
public List<ListenerDefinition> listeners() {
return null;
}
}