/**
* 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.internal;
import com.fasterxml.jackson.jaxrs.base.JsonMappingExceptionMapper;
import com.fasterxml.jackson.jaxrs.base.JsonParseExceptionMapper;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import com.google.inject.AbstractModule;
import io.nuun.kernel.api.plugin.InitState;
import io.nuun.kernel.api.plugin.context.InitContext;
import io.nuun.kernel.api.plugin.request.ClasspathScanRequest;
import org.kametic.specifications.Specification;
import org.seedstack.seed.core.SeedRuntime;
import org.seedstack.seed.core.internal.AbstractSeedPlugin;
import org.seedstack.seed.rest.RelRegistry;
import org.seedstack.seed.rest.RestConfig;
import org.seedstack.seed.rest.internal.exceptionmapper.AuthenticationExceptionMapper;
import org.seedstack.seed.rest.internal.exceptionmapper.AuthorizationExceptionMapper;
import org.seedstack.seed.rest.internal.exceptionmapper.InternalErrorExceptionMapper;
import org.seedstack.seed.rest.internal.exceptionmapper.WebApplicationExceptionMapper;
import org.seedstack.seed.rest.internal.hal.RelRegistryImpl;
import org.seedstack.seed.rest.internal.jsonhome.JsonHome;
import org.seedstack.seed.rest.internal.jsonhome.JsonHomeRootResource;
import org.seedstack.seed.rest.internal.jsonhome.Resource;
import org.seedstack.seed.rest.spi.RestProvider;
import org.seedstack.seed.rest.spi.RootResource;
import javax.servlet.ServletContext;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Variant;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public class RestPlugin extends AbstractSeedPlugin implements RestProvider {
private final Map<Variant, Class<? extends RootResource>> rootResourcesByVariant = new HashMap<>();
private RestConfig restConfig;
private boolean enabled;
private ServletContext servletContext;
private RelRegistry relRegistry;
private JsonHome jsonHome;
private Collection<Class<?>> resources;
private Collection<Class<?>> providers;
@Override
public String name() {
return "rest";
}
@Override
protected void setup(SeedRuntime seedRuntime) {
servletContext = seedRuntime.contextAs(ServletContext.class);
}
@Override
public Collection<ClasspathScanRequest> classpathScanRequests() {
return classpathScanRequestBuilder()
.specification(JaxRsResourceSpecification.INSTANCE)
.specification(JaxRsProviderSpecification.INSTANCE)
.build();
}
@Override
public InitState initialize(InitContext initContext) {
Map<Specification, Collection<Class<?>>> scannedClasses = initContext.scannedTypesBySpecification();
restConfig = getConfiguration(RestConfig.class);
resources = scannedClasses.get(JaxRsResourceSpecification.INSTANCE);
providers = scannedClasses.get(JaxRsProviderSpecification.INSTANCE);
initializeHypermedia();
if (servletContext != null) {
addJacksonProviders(providers);
configureExceptionMappers();
if (restConfig.isJsonHome()) {
// The typed locale parameter resolves constructor ambiguity when the JAX-RS 2.0 spec is in the classpath
addRootResourceVariant(new Variant(new MediaType("application", "json"), (Locale) null, null), JsonHomeRootResource.class);
}
enabled = true;
}
return InitState.INITIALIZED;
}
private void configureExceptionMappers() {
if (!restConfig.exceptionMapping().isSecurity()) {
providers.remove(AuthenticationExceptionMapper.class);
providers.remove(AuthorizationExceptionMapper.class);
}
if (!restConfig.exceptionMapping().isAll()) {
providers.remove(WebApplicationExceptionMapper.class);
providers.remove(InternalErrorExceptionMapper.class);
}
}
private void addJacksonProviders(Collection<Class<?>> providers) {
providers.add(JsonMappingExceptionMapper.class);
providers.add(JsonParseExceptionMapper.class);
providers.add(JacksonJsonProvider.class);
providers.add(JacksonJaxbJsonProvider.class);
}
private void initializeHypermedia() {
ResourceScanner resourceScanner = new ResourceScanner(restConfig, servletContext).scan(resources);
Map<String, Resource> resourceMap = resourceScanner.jsonHomeResources();
relRegistry = new RelRegistryImpl(resourceScanner.halLinks());
jsonHome = new JsonHome(resourceMap);
}
@Override
public Object nativeUnitModule() {
return new AbstractModule() {
@Override
protected void configure() {
install(new HypermediaModule(jsonHome, relRegistry));
if (enabled) {
install(new RestModule(restConfig, filterResourceClasses(resources), providers, rootResourcesByVariant));
}
}
};
}
private Collection<Class<?>> filterResourceClasses(Collection<Class<?>> resourceClasses) {
if (!rootResourcesByVariant.isEmpty()) {
return resourceClasses;
} else {
HashSet<Class<?>> filteredResourceClasses = new HashSet<>(resourceClasses);
filteredResourceClasses.remove(RootResourceDispatcher.class);
return filteredResourceClasses;
}
}
public void addRootResourceVariant(Variant variant, Class<? extends RootResource> rootResource) {
rootResourcesByVariant.put(variant, rootResource);
}
public RestConfig getRestConfig() {
return restConfig;
}
public boolean isEnabled() {
return enabled;
}
@Override
public Set<Class<?>> resources() {
return resources != null ? new HashSet<>(filterResourceClasses(resources)) : new HashSet<>();
}
@Override
public Set<Class<?>> providers() {
return providers != null ? new HashSet<>(providers) : new HashSet<>();
}
}