All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.neba.core.mvc.MvcContext Maven / Gradle / Ivy

/**
 * Copyright 2013 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 the "License";
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
**/

package io.neba.core.mvc;

import org.apache.sling.api.SlingHttpServletResponse;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import static org.springframework.beans.factory.BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR;
import static org.springframework.web.servlet.DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME;

/**
 * 

* Configures a bundle-specific {@link ApplicationContext} with the Spring MVC infrastructure beans usually * provided to {@link org.springframework.web.context.WebApplicationContext web application contexts} by the * {@link DispatcherServlet}. Subsequently * {@link DispatcherServlet#initStrategies(org.springframework.context.ApplicationContext) initializes} * a context-specific {@link DispatcherServlet} instance with the so configured context. The resulting * dispatcher servlet is a fully-featured dispatcher servlet for the specific bundle, with the * exception of {@link DispatcherServlet#setPublishEvents(boolean) event publication}, which is disabled * as it requires a {@link org.springframework.web.context.WebApplicationContext}. *

* The configured context may provide custom MVC infrastructure beans, e.g. * by means of a <mvc:.../> XML configuration in their application context XML. * Like the {@link DispatcherServlet}, the {@link MvcContext} will only provide MVC infrastructure * beans if no suitable beans exist in the provided context. * * @author Olaf Otto */ public class MvcContext implements ApplicationListener { private volatile boolean dispatcherServletInitialized = false; private volatile boolean mvcInfrastructureInitialized = false; private ApplicationContext context; /** * Weakens visibility restrictions of the original {@link DispatcherServlet} * and {@link DispatcherServlet#setPublishEvents(boolean) disables event publication} * as event publication requires the presence of a {@link org.springframework.web.context.WebApplicationContext}, * whereas an {@link org.eclipse.gemini.blueprint.context.support.OsgiBundleXmlApplicationContext} is * used by gemini-blueprint. * * @author Olaf Otto */ private static class ContextSpecificDispatcherServlet extends DispatcherServlet { private ServletConfig servletConfig; private ContextSpecificDispatcherServlet() { super(); setPublishEvents(false); setDispatchOptionsRequest(true); setDispatchTraceRequest(true); } @Override public ServletConfig getServletConfig() { return this.servletConfig; } public void setServletConfig(ServletConfig servletConfig) { this.servletConfig = servletConfig; } @Override protected void initStrategies(ApplicationContext context) { super.initStrategies(context); } protected boolean hasHandlerFor(HttpServletRequest request) { try { return super.getHandler(request) != null; } catch (Exception e) { throw new RuntimeException("Unable to lookup a handler for " + request + ".", e); } } } private final ConfigurableListableBeanFactory factory; private final ContextSpecificDispatcherServlet dispatcherServlet; /** * @param factory must not be null. */ public MvcContext(ConfigurableListableBeanFactory factory) { if (factory == null) { throw new IllegalArgumentException("Constructor parameter factory must not be null."); } this.dispatcherServlet = new ContextSpecificDispatcherServlet(); this.factory = factory; } /** * Configures the context's bean factory with MVC infrastructure beans. * * @param event must not be null. */ @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextRefreshedEvent) { synchronized (this) { this.context = ((ContextRefreshedEvent) event).getApplicationContext(); configureMultipartResolver(); configureExceptionResolvers(); configureHandlerAdapters(); registerCustomArgumentResolvers(); configureHandlerMappings(); configureViewResolvers(); this.mvcInfrastructureInitialized = true; } } } /** * @param request must not be null. * * @return true if this context has been * {@link #onApplicationEvent(ApplicationEvent) initialized} * and the {@link DispatcherServlet} contains a * {@link DispatcherServlet#getHandler(javax.servlet.http.HttpServletRequest) handler for the request}. */ public boolean isResponsibleFor(HttpServletRequest request) { return this.mvcInfrastructureInitialized && this.dispatcherServletInitialized && this.dispatcherServlet.hasHandlerFor(request); } /** * Delegates to the context specific {@link DispatcherServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}. * Must only be called if {@link #isResponsibleFor(javax.servlet.http.HttpServletRequest)} returns true. * * @param request must not be null. * @param response request must not be null. */ public void service(SlingMvcServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException { this.dispatcherServlet.service(request, response); } /** * Registers the custom argument resolvers if a {@link RequestMappingHandlerAdapter} * is present in the factory. */ private void registerCustomArgumentResolvers() { RequestMappingHandlerAdapter requestMappingHandlerAdapter = this.factory.getBean(RequestMappingHandlerAdapter.class); if (requestMappingHandlerAdapter != null) { HandlerMethodArgumentResolverComposite argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers(); if (argumentResolvers == null) { throw new IllegalStateException("No argument resolvers found in " + requestMappingHandlerAdapter + ". It appears the handler was not initialized by the application context."); } // Add Sling-specific argument resolvers first List resolvers = new LinkedList(); resolvers.add(new RequestPathInfoArgumentResolver()); resolvers.add(new ResourceResolverArgumentResolver()); resolvers.add(new ResourceParamArgumentResolver()); // Subsequently add all existing argument resolvers (they are order-sensitive, ending with a catch-all resolver, // thus the custom resolvers have to go first) resolvers.addAll(argumentResolvers.getResolvers()); requestMappingHandlerAdapter.setArgumentResolvers(resolvers); } } /** * Discovers existing {@link org.springframework.web.servlet.HandlerAdapter handler adapters} in the provided * context. Provides the default adapters (see original dispatcher servlet) in case no adapters * exist in the context. */ private void configureHandlerAdapters() { Map handlerAdapters = this.factory.getBeansOfType(HandlerAdapter.class); if (handlerAdapters.isEmpty()) { defineBean(HttpRequestHandlerAdapter.class); defineBean(RequestMappingHandlerAdapter.class); } } /** * Discovers existing {@link org.springframework.web.servlet.HandlerExceptionResolver exception resolvers} in the provided * context. Provides the default resolvers (see original dispatcher servlet) in case no resolvers * exist in the context. */ private void configureExceptionResolvers() { Map resolvers = this.factory.getBeansOfType(HandlerExceptionResolver.class); if (resolvers.isEmpty()) { defineBean(ExceptionHandlerExceptionResolver.class); defineBean(ResponseStatusExceptionResolver.class); DefaultHandlerExceptionResolver defaultResolver = defineBean(DefaultHandlerExceptionResolver.class); defaultResolver.setWarnLogCategory("mvc"); } } /** * Discovers existing {@link org.springframework.web.servlet.HandlerMapping handler mappings} in the provided * context. Provides the default mappings (see original dispatcher servlet) in case no mappings * exist in the context. */ private void configureHandlerMappings() { Map handlerMappings = this.factory.getBeansOfType(HandlerMapping.class); if (handlerMappings.isEmpty()) { defineBean(BeanNameUrlHandlerMapping.class); defineBean(RequestMappingHandlerMapping.class); } } /** * Discovers existing {@link org.springframework.web.servlet.ViewResolver view resolver} in the provided * context. Provides a special {@link NebaViewResolver} in case no resolver * exist in the context. */ private void configureViewResolvers() { Map resolvers = this.factory.getBeansOfType(ViewResolver.class); if (resolvers.isEmpty()) { defineBean(NebaViewResolver.class); } } /** * Discovers existing {@link org.springframework.web.multipart.MultipartResolver multipart resolvers} * in the provided context. Provides a special {@link io.neba.core.mvc.SlingMultipartResolver} * in case no resolver exists in the context. */ private void configureMultipartResolver() { if (!hasBean(MultipartResolver.class)) { defineBean(SlingMultipartResolver.class, MULTIPART_RESOLVER_BEAN_NAME); } } private String generateBeanNameFor(Class type) { return type.getName() + GENERATED_BEAN_NAME_SEPARATOR + "0"; } private T defineBean(Class type) { return defineBean(type, generateBeanNameFor(type)); } private T defineBean(Class type, String beanName) { T bean = this.factory.createBean(type); this.factory.registerSingleton(beanName, bean); return bean; } public synchronized void initializeDispatcherServlet(ServletConfig config) { if (mustInitializeDispatcherServlet()) { this.dispatcherServlet.setServletConfig(config); this.dispatcherServlet.initStrategies(this.context); this.dispatcherServletInitialized = true; } } public boolean mustInitializeDispatcherServlet() { return !this.dispatcherServletInitialized && this.mvcInfrastructureInitialized; } private boolean hasBean(Class type) { return !this.factory.getBeansOfType(type).isEmpty(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy