/**
* 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.web.internal;
import com.google.common.collect.Lists;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceFilter;
import io.nuun.kernel.api.plugin.InitState;
import io.nuun.kernel.api.plugin.context.Context;
import io.nuun.kernel.api.plugin.context.InitContext;
import org.seedstack.seed.SeedException;
import org.seedstack.seed.core.SeedRuntime;
import org.seedstack.seed.core.internal.AbstractSeedPlugin;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletContext;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* This plugin provides support for servlet-based Web applications.
*/
public class WebPlugin extends AbstractSeedPlugin {
private static final Logger LOGGER = LoggerFactory.getLogger(WebPlugin.class);
private static final String WEB_INF_LIB = "/WEB-INF/lib";
private static final String WEB_INF_CLASSES = "/WEB-INF/classes";
private final List<FilterDefinition> filterDefinitions = new ArrayList<>();
private final List<ServletDefinition> servletDefinitions = new ArrayList<>();
private final List<ListenerDefinition> listenerDefinitions = new ArrayList<>();
private ServletContext servletContext;
@Override
public String name() {
return "web";
}
@Override
protected void setup(SeedRuntime seedRuntime) {
servletContext = seedRuntime.contextAs(ServletContext.class);
}
@Override
public Set<URL> computeAdditionalClasspathScan() {
Set<URL> additionalUrls = new HashSet<>();
if (servletContext != null) {
Set<URL> webLibraries = resolveWebLibraries();
if (webLibraries.isEmpty()) {
LOGGER.debug("No Web library found for scanning");
} else {
for (URL webLibrary : webLibraries) {
LOGGER.trace("Resolved Web library {}", webLibrary);
}
LOGGER.debug("Found {} Web libraries for scanning", webLibraries.size());
}
additionalUrls.addAll(webLibraries);
Set<URL> webClassesLocations = resolveWebClassesLocations();
if (webClassesLocations.isEmpty()) {
LOGGER.debug("No {} location found for scanning", WEB_INF_CLASSES);
} else {
for (URL webClassesLocation : webClassesLocations) {
LOGGER.trace("Resolved '{}' location: {}", WEB_INF_CLASSES, webClassesLocation);
}
LOGGER.debug("Found {} '{}' locations for scanning", webClassesLocations.size(), WEB_INF_CLASSES);
}
additionalUrls.addAll(webClassesLocations);
}
return additionalUrls;
}
@Override
public Collection<Class<?>> dependencies() {
return Lists.newArrayList(WebProvider.class);
}
@Override
public InitState initialize(InitContext initContext) {
if (servletContext != null) {
List<WebProvider> webProviders = initContext.dependencies(WebProvider.class);
for (WebProvider webProvider : webProviders) {
List<FilterDefinition> filters = webProvider.filters();
if (filters != null) {
filterDefinitions.addAll(filters);
}
List<ServletDefinition> servlets = webProvider.servlets();
if (servlets != null) {
servletDefinitions.addAll(servlets);
}
List<ListenerDefinition> listeners = webProvider.listeners();
if (listeners != null) {
listenerDefinitions.addAll(listeners);
}
}
// Sort filter according to the priority in their definition
filterDefinitions.sort(Collections.reverseOrder((o1, o2) -> new Integer(o1.getPriority()).compareTo(o2.getPriority())));
// Sort listeners according to the priority in their definition
listenerDefinitions.sort(Collections.reverseOrder((o1, o2) -> new Integer(o1.getPriority()).compareTo(o2.getPriority())));
}
return InitState.INITIALIZED;
}
@Override
public Object nativeUnitModule() {
return new WebModule();
}
@Override
public void start(Context context) {
if (servletContext != null) {
ServletContextConfigurer servletContextConfigurer = new ServletContextConfigurer(servletContext, context.applicationObjectGraph().as(Injector.class));
for (ListenerDefinition listenerDefinition : listenerDefinitions) {
LOGGER.trace("Registering servlet listener {}", listenerDefinition.getListenerClass());
servletContextConfigurer.addListener(listenerDefinition);
}
LOGGER.debug("Registered {} servlet listener(s)", listenerDefinitions.size());
LOGGER.trace("Registering Guice servlet filter");
servletContextConfigurer.addFilter(buildGuiceFilterDefinition());
for (FilterDefinition filterDefinition : filterDefinitions) {
LOGGER.trace("Registering servlet filter {} with {} priority", filterDefinition.getFilterClass(), filterDefinition.getPriority());
servletContextConfigurer.addFilter(filterDefinition);
}
LOGGER.debug("Registered {} servlet filter(s)", filterDefinitions.size() + 1);
for (ServletDefinition servletDefinition : servletDefinitions) {
LOGGER.trace("Registering servlet {}", servletDefinition.getServletClass());
servletContextConfigurer.addServlet(servletDefinition);
}
LOGGER.debug("Registered {} servlet(s)", servletDefinitions.size());
}
}
private FilterDefinition buildGuiceFilterDefinition() {
FilterDefinition guiceFilter = new FilterDefinition("guice", GuiceFilter.class);
guiceFilter.setAsyncSupported(true);
guiceFilter.addMappings(new FilterDefinition.Mapping("/*"));
return guiceFilter;
}
private Set<URL> resolveWebLibraries() {
final Set<URL> resolvedUrls = new HashSet<>();
Set<String> resourcePaths = servletContext.getResourcePaths(WEB_INF_LIB);
if (resourcePaths != null) {
for (String resourcePath : resourcePaths) {
try {
URL resolvedURL = servletContext.getResource(resourcePath);
if (resolvedURL != null) {
resolvedUrls.add(resolvedURL);
} else {
LOGGER.warn("Ignoring unresolvable Web library {}", resourcePath);
}
} catch (Exception e) {
throw SeedException.wrap(e, WebErrorCode.CANNOT_RESOLVE_WEB_RESOURCE_LOCATION)
.put("path", resourcePath);
}
}
}
return resolvedUrls;
}
private Set<URL> resolveWebClassesLocations() {
final Set<URL> resolvedUrls = new HashSet<>();
Set<String> resourcePaths = servletContext.getResourcePaths(WEB_INF_CLASSES);
if (resourcePaths != null) {
for (String resourcePath : resourcePaths) {
if (resourcePath.startsWith(WEB_INF_CLASSES)) {
String suffix = resourcePath.substring(WEB_INF_CLASSES.length());
try {
URL resource = servletContext.getResource(resourcePath);
if (resource != null) {
String resourceAsString = resource.toExternalForm();
if (resourceAsString.endsWith(suffix)) {
resolvedUrls.add(new URL(resourceAsString.substring(0, resourceAsString.length() - suffix.length())));
} else {
LOGGER.warn("Ignoring invalid '{}' location: {}", WEB_INF_CLASSES, resourcePath);
}
} else {
LOGGER.warn("Ignoring unresolvable '{}' location: {}", WEB_INF_CLASSES, resourcePath);
}
} catch (Exception e) {
throw SeedException.wrap(e, WebErrorCode.CANNOT_RESOLVE_WEB_RESOURCE_LOCATION)
.put("path", resourcePath);
}
} else {
LOGGER.warn("Ignoring invalid '{}' location: {}", WEB_INF_CLASSES, resourcePath);
}
}
}
return resolvedUrls;
}
}