org.codehaus.enunciate.modules.spring_app.SpringAppDeploymentModule Maven / Gradle / Ivy
/*
* Copyright 2006-2008 Web Cohesion
*
* 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 org.codehaus.enunciate.modules.spring_app;
import freemarker.template.TemplateException;
import org.apache.commons.digester.RuleSet;
import org.codehaus.enunciate.EnunciateException;
import org.codehaus.enunciate.apt.EnunciateClasspathListener;
import org.codehaus.enunciate.apt.EnunciateFreemarkerModel;
import org.codehaus.enunciate.contract.validation.Validator;
import org.codehaus.enunciate.main.Enunciate;
import org.codehaus.enunciate.main.webapp.BaseWebAppFragment;
import org.codehaus.enunciate.modules.FreemarkerDeploymentModule;
import org.codehaus.enunciate.modules.spring_app.config.GlobalServiceInterceptor;
import org.codehaus.enunciate.modules.spring_app.config.HandlerInterceptor;
import org.codehaus.enunciate.modules.spring_app.config.SpringAppRuleSet;
import org.codehaus.enunciate.modules.spring_app.config.SpringImport;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
/**
* Spring App Module
*
* The spring app deployment module produces the configuration files and application extensions needed to apply
* the Spring container to the Web service application.
*
*
* - steps
* - application configuration
* - artifacts
*
*
* Steps
*
* generate
*
* The "generate" step generates the Spring
* configuration file and application extensions. Refer to configuration
* to learn how to customize these things.
*
* The "generate" step is the only relevant step in the spring app deployment module.
*
* Configuration
*
*
* - structure
* - attributes
* - elements
*
*
*
*
* The configuration for the Spring App deployment module is specified by the "spring-app" child element under the "modules" element
* of the enunciate configuration file.
*
* Structure
*
* The following example shows the structure of the configuration elements for this module. Note that this shows only the structure.
* Some configuration elements don't make sense when used together.
*
*
* <enunciate>
* <modules>
* <spring-app contextLoaderListenerClass="..."
* applicationContextFilename="..." contextConfigLocation="..."
* springVersion="...">
*
* <springImport file="..." uri="..."/>
* <springImport file="..." uri="..."/>
* ...
*
* <globalServiceInterceptor interceptorClass="..." beanName="..."/>
* <globalServiceInterceptor interceptorClass="..." beanName="..."/>
* ...
*
* <handlerInterceptor interceptorClass="..." beanName="..."/>
* <handlerInterceptor interceptorClass="..." beanName="..."/>
* ...
*
* <handlerMapping pattern="..." beanName="..."/>
* <handlerMapping pattern="..." beanName="..."/>
* ...
*
* </spring-app>
* </modules>
* </enunciate>
*
*
* attributes
*
*
* - The "contextLoaderListenerClass" attribute specifies that FQN of the class to use as the Spring context loader listener. The default is "org.springframework.web.context.ContextLoaderListener".
* - The "applicationContextFilename" attribute specifies the name of the Enunciate-generated application context file. The default is "applicationContext.xml".
* - The "contextConfigLocation" attribute specifies the value of the contextConfigLocation init parameter supplied to the Spring
* ContextLoaderListener. The default is "/WEB-INF/" + applicationContextFilename.
* - The "springVersion" attribute specifies the spring version to use. If not set, an attempt will be made to autodetect it.
*
*
* The "springImport" element
*
* The "springImport" element is used to specify a spring configuration file that will be imported by the main
* spring servlet config. It supports the following attributes:
*
*
* - The "file" attribute specifies the spring import file on the filesystem. It will be copied to the WEB-INF directory.
* - The "uri" attribute specifies the URI to the spring import file. The URI will not be resolved at compile-time, nor will anything be copied to the
* WEB-INF directory. The value of this attribute will be used to reference the spring import file in the main config file. This attribute is useful
* to specify an import file on the classpath, e.g. "classpath:com/myco/spring/config.xml".
*
*
* One use of specifying spring a import file is to wrap your endpoints with spring interceptors. This can be done
* by simply declaring a bean that is an instance of your endpoint class, which can be advised as needed.
*
* It's important to note that the type on which the bean context will be searched is the type of the endpoint interface, and then only if it exists.
* If there are more than one beans that are assignable to the endpoint interface, the bean that is named the name of the service will be used. Otherwise,
* the deployment of your endpoint will fail.
*
* The same procedure can be used to specify the beans to use as REST endpoints. In this case,
* the bean context will be searched for each REST interface that the endpoint implements. If there is a bean that implements that interface, it will
* used instead of the default implementation. If there is more than one, the bean that is named the same as the REST endpoint will be used.
*
* There also exists a mechanism to add certain AOP interceptors to all service endpoint beans. Such interceptors are referred to as "global service
* interceptors." This can be done by using the "globalServiceInterceptor" element (see below), or by simply creating an interceptor that implements
* org.codehaus.enunciate.modules.spring_app.EnunciateServiceAdvice or org.codehaus.enunciate.modules.spring_app.EnunciateServiceAdvisor and declaring it in your
* imported spring beans file.
*
* Each global interceptor has an order. The default order is 0 (zero). If a global service interceptor implements org.springframework.core.Ordered, the
* order will be respected. As global service interceptors are added, it will be assigned a position in the chain according to it's order. Interceptors
* of the same order will be ordered together according to their position in the config file, with priority to those declared by the "globalServiceInterceptor"
* element, then to instances of org.codehaus.enunciate.modules.spring_app.EnunciateServiceAdvice, then to instances of
* org.codehaus.enunciate.modules.spring_app.EnunciateServiceAdvisor.
*
* For more information on spring bean configuration and interceptor advice, see
* the spring reference documentation.
*
* The "globalServiceInterceptor" element
*
* The "globalServiceInterceptor" element is used to specify a Spring interceptor (instance of org.aopalliance.aop.Advice or
* org.springframework.aop.Advisor) that is to be injected on all service endpoint beans.
*
*
* - The "interceptorClass" attribute specified the class of the interceptor.
*
- The "beanName" attribute specifies the bean name of the interceptor.
*
*
* The "handlerInterceptor" element
*
* The "handlerInterceptor" element is used to specify a Spring interceptor (instance of org.springframework.web.servlet.HandlerInterceptor)
* that is to be injected on the handler mapping.
*
*
* - The "interceptorClass" attribute specifies the class of the interceptor.
*
- The "beanName" attribute specifies the bean name of the interceptor.
*
*
* For more information on spring bean configuration and interceptor advice, see
* the spring reference documentation.
*
* The "handlerMapping" element
*
* The "handlerMapping" element is used to specify a custom Spring handler mapping.
*
*
* - The "pattern" attribute specifies the pattern that maps to the handler.
*
- The "beanName" attribute specifies the bean name of the handler.
*
*
* For more information on spring handler mappings, see
* the spring reference documentation.
*
* Artifacts
*
* The spring app deployment module exports no artifacts.
*
* @author Ryan Heaton
* @docFileName module_spring_app.html
*/
public class SpringAppDeploymentModule extends FreemarkerDeploymentModule implements EnunciateClasspathListener {
private final List springImports = new ArrayList();
private final List globalServiceInterceptors = new ArrayList();
private final List handlerInterceptors = new ArrayList();
private String applicationContextFilename = "applicationContext.xml";
private String contextConfigLocation = null;
private String contextLoaderListenerClass = "org.springframework.web.context.ContextLoaderListener";
private boolean enableSecurity = false;
private boolean factoryBeanFound = false;
private boolean spring3 = false;
/**
* @return "spring-app"
*/
@Override
public String getName() {
return "spring-app";
}
/**
* @return The URL to "spring-servlet.fmt"
*/
protected URL getApplicationContextTemplateURL() {
return SpringAppDeploymentModule.class.getResource("applicationContext.xml.fmt");
}
public void onClassesFound(Set classes) {
factoryBeanFound |= classes.contains("org.codehaus.enunciate.modules.spring_app.ServiceEndpointFactoryBean");
//we'll key off the Converter class since that's new in Spring 3.
spring3 |= classes.contains("org.springframework.core.convert.converter.Converter");
}
@Override
public void init(Enunciate enunciate) throws EnunciateException {
super.init(enunciate);
if (!isDisabled() && isEnableSecurity()) {
throw new EnunciateException("As of 1.23, enunciate-specific spring security configuration has been DEPRECATED and will be removed in a future release. Please see http://goo.gl/1S94J for more information.");
}
}
@Override
public void initModel(EnunciateFreemarkerModel model) {
super.initModel(model);
if (!isDisabled()) {
if (!factoryBeanFound) {
warn("The Spring module is enabled, but the Enunciate-Spring runtime classes weren't found on the Enunciate classpath. This could be fatal to the runtime application...");
}
}
}
@Override
public void doFreemarkerGenerate() throws IOException, TemplateException {
if (!enunciate.isUpToDateWithSources(getWebInfDir())) {
EnunciateFreemarkerModel model = getModel();
//standard spring configuration:
model.put("springImports", getSpringImportURIs());
model.put("applicationContextFilename", getApplicationContextFilename());
model.put("spring3", this.spring3);
Object docsDir = enunciate.getProperty("docs.webapp.dir");
if (docsDir == null) {
docsDir = "";
}
model.put("docsDir", docsDir);
if (!globalServiceInterceptors.isEmpty()) {
for (GlobalServiceInterceptor interceptor : this.globalServiceInterceptors) {
if ((interceptor.getBeanName() == null) && (interceptor.getInterceptorClass() == null)) {
throw new IllegalStateException("A global interceptor must have either a bean name or a class set.");
}
}
model.put("globalServiceInterceptors", this.globalServiceInterceptors);
}
if (!handlerInterceptors.isEmpty()) {
for (HandlerInterceptor interceptor : this.handlerInterceptors) {
if ((interceptor.getBeanName() == null) && (interceptor.getInterceptorClass() == null)) {
throw new IllegalStateException("A handler interceptor must have either a bean name or a class set.");
}
}
model.put("handlerInterceptors", this.handlerInterceptors);
}
model.setFileOutputDirectory(getWebInfDir());
processTemplate(getApplicationContextTemplateURL(), model);
copySpringConfig();
}
else {
info("Skipping generation of spring config files as everything appears up-to-date...");
}
BaseWebAppFragment webAppFragment = new BaseWebAppFragment(getName());
webAppFragment.setBaseDir(getGenerateDir());
ArrayList servletListeners = new ArrayList();
servletListeners.add(getContextLoaderListenerClass());
servletListeners.add("org.codehaus.enunciate.modules.spring_app.SpringComponentPostProcessor");
webAppFragment.setListeners(servletListeners);
Map contextParams = new HashMap();
String contextConfigLocation = getContextConfigLocation();
if (contextConfigLocation == null) {
contextConfigLocation = "/WEB-INF/" + getApplicationContextFilename();
}
contextParams.put("contextConfigLocation", contextConfigLocation);
webAppFragment.setContextParameters(contextParams);
getEnunciate().addWebAppFragment(webAppFragment);
}
/**
* Copy the spring application context and servlet config from the build dir to the WEB-INF directory.
*/
protected void copySpringConfig() throws IOException {
for (SpringImport springImport : springImports) {
//copy the extra spring import files to the WEB-INF directory to be imported.
if (springImport.getFile() != null) {
File importFile = enunciate.resolvePath(springImport.getFile());
String name = importFile.getName();
name = resolveSpringImportFileName(name);
enunciate.copyFile(importFile, new File(getWebInfDir(), name));
}
}
}
/**
* Get the string form of the spring imports that have been configured.
*
* @return The string form of the spring imports that have been configured.
*/
protected ArrayList getSpringImportURIs() {
ArrayList springImportURIs = new ArrayList(this.springImports.size());
for (SpringImport springImport : springImports) {
if (springImport.getFile() != null) {
if (springImport.getUri() != null) {
throw new IllegalStateException("A spring import configuration must specify a file or a URI, but not both.");
}
String fileName = new File(springImport.getFile()).getName();
fileName = resolveSpringImportFileName(fileName);
springImportURIs.add(fileName);
}
else if (springImport.getUri() != null) {
springImportURIs.add(springImport.getUri());
}
else {
throw new IllegalStateException("A spring import configuration must specify either a file or a URI.");
}
}
return springImportURIs;
}
/**
* Resolves the application context file name (in case there's a conflict).
*
* @param fileName The file name.
* @return The resolved file name.
*/
protected String resolveSpringImportFileName(String fileName) {
if (!"applicationContext.xml".equals(getApplicationContextFilename())) {
//if we're not using the default applicationContext.xml filename, we'll assume the user knows what they're doing.
return fileName;
}
if ("applicationContext.xml".equalsIgnoreCase(fileName)) {
fileName = "applicationContext-" + getModel().getEnunciateConfig().getLabel() + ".xml";
}
return fileName;
}
@Override
public boolean isDisabled() {
if (super.isDisabled()) {
return true;
}
else if (getModelInternal() != null && getModelInternal().getEnunciateConfig() != null && getModelInternal().getEnunciateConfig().getWebAppConfig() != null && getModelInternal().getEnunciateConfig().getWebAppConfig().isDisabled()) {
debug("Module '%s' is disabled because the web application processing has been disabled.", getName());
return true;
}
return false;
}
/**
* The list of spring imports.
*
* @return The list of spring imports.
*/
public List getSpringImports() {
return springImports;
}
/**
* Add a spring import.
*
* @param springImports The spring import to add.
*/
public void addSpringImport(SpringImport springImports) {
this.springImports.add(springImports);
}
/**
* Add a global service interceptor to the spring configuration.
*
* @param interceptorConfig The interceptor configuration.
*/
public void addGlobalServiceInterceptor(GlobalServiceInterceptor interceptorConfig) {
this.globalServiceInterceptors.add(interceptorConfig);
}
/**
* Add a handler interceptor to the spring configuration.
*
* @param interceptorConfig The interceptor configuration.
*/
public void addHandlerInterceptor(HandlerInterceptor interceptorConfig) {
this.handlerInterceptors.add(interceptorConfig);
}
/**
* The class to use as the context loader listener.
*
* @return The class to use as the context loader listener.
*/
public String getContextLoaderListenerClass() {
return contextLoaderListenerClass;
}
/**
* The class to use as the context loader listener.
*
* @param contextLoaderListenerClass The class to use as the context loader listener.
*/
public void setContextLoaderListenerClass(String contextLoaderListenerClass) {
this.contextLoaderListenerClass = contextLoaderListenerClass;
}
/**
* The name of the application context file.
*
* @return The name of the application context file.
*/
public String getApplicationContextFilename() {
return applicationContextFilename;
}
/**
* The name of the application context file.
*
* @param applicationContextFilename The name of the application context file.
*/
public void setApplicationContextFilename(String applicationContextFilename) {
this.applicationContextFilename = applicationContextFilename;
}
/**
* The context config location.
*
* @return The context config location.
*/
public String getContextConfigLocation() {
return contextConfigLocation;
}
/**
* The context config location.
*
* @param contextConfigLocation The context config location.
*/
public void setContextConfigLocation(String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
/**
* Whether to enable security.
*
* @return Whether to enable security.
*/
public boolean isEnableSecurity() {
return enableSecurity;
}
/**
* Whether to enable security.
*
* @param enableSecurity Whether to enable security.
*/
public void setEnableSecurity(boolean enableSecurity) {
this.enableSecurity = enableSecurity;
}
/**
* The spring version to use.
*
* @param version The spring version to use.
*/
public void setSpringVersion(String version) {
this.spring3 = version.startsWith("3");
}
/**
* @return 200
*/
@Override
public int getOrder() {
return 200;
}
@Override
public RuleSet getConfigurationRules() {
return new SpringAppRuleSet();
}
@Override
public Validator getValidator() {
return null;
}
/**
* The directory where the config files are generated.
*
* @return The directory where the config files are generated.
*/
protected File getWebInfDir() {
return new File(getGenerateDir(), "WEB-INF");
}
}