Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specify a custom web.xml to an embedded tomcat

Is there a way to specify a different web.xml from the standard WEB-INF/web.xml when using an embedded tomcat instance?

I would like to put a web.xml in my src/test/resources (or some other area) and refer to that web.xml when starting the embedded tomcat.

Here is my existing code to start the tomcat instance

tomcat = new Tomcat();
String baseDir = ".";
tomcat.setPort(8080);
tomcat.setBaseDir(baseDir);
tomcat.getHost().setAppBase(baseDir);
tomcat.getHost().setAutoDeploy(true);
tomcat.enableNaming();

Context ctx = tomcat.addWebApp(tomcat.getHost(), "/sandbox-web", "src\\main\\webapp");
File configFile = new File("src\\main\\webapp\\META-INF\\context.xml");
ctx.setConfigFile(configFile.toURI().toURL());

tomcat.start();

I am starting this server from a tomcat instance and I would like to do the following when running unit tests

  • turn off the contextConfigLocation
  • specify a custom ContextLoaderListener that sets the parent ApplicationContext of the embedded tomcat.

This file might be specified like so:

File webXmlFile = new File("src\\test\\resources\\embedded-web.xml");

Edit

After much frustration I realized that no matter what I do, I cannot persuade tomcat from looking in WEB-INF for web.xml. It appears that I must ignore the web.xml altogether and set the items in the web.xml programmatically.

I ended up with this configuration:

cucumber.xml for configuring tests

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="applicationContextProvider" class="ca.statcan.icos.sandbox.ApplicationContextProvider"/>

    <bean id="sandBoxDataSource" class="org.apache.tomcat.dbcp.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
        <property name="url" value="jdbc:hsqldb:mem:testdb;shutdown=true;" />
        <property name="username" value="SA" />
        <property name="password" value="" />
    </bean>

    <!-- Support for JPA related annotation support (@PersistenceUnit and @PersistenceContext) -->
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

    <!-- JTA Configuration -->
    <bean id="jtaTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
        init-method="init" destroy-method="close">
        <property name="forceShutdown"><value>true</value></property>
    </bean>

    <bean id="jtaUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp" />

    <bean id="springTransactionManager"
        class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager" ref="jtaTransactionManager" />
        <property name="userTransaction" ref="jtaUserTransaction" />
    </bean>

    <!-- JPA Entity Manager configuration -->
    <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
        lazy-init="true">
        <property name="persistenceUnitName" value="sandBox" />    
        <property name="dataSource" ref="sandBoxDataSource" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="database" value="SQL_SERVER" />
                <property name="showSql" value="true" />
                <property name="generateDdl" value="true" />
            </bean>
        </property>
        <property name="jpaPropertyMap">
            <props>
                <prop key="hibernate.archive.autodetection">class</prop>
                <prop key="hibernate.cache.use_second_level_cache">false</prop>
                <prop key="hibernate.cache.use_query_cache">false</prop>
                <!--  Second Level Cache : EHCache in dev
                <prop key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</prop> -->
                <prop key="hibernate.hbm2ddl.auto">create</prop>
             </props>
        </property>
    </bean>

    <bean class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <import resource="classpath:META-INF/applicationContext-core.xml" />
    <import resource="classpath:META-INF/applicationContext-web.xml" />

</beans>

applicationContext-core.xml - where the services are configured

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd"
    default-autowire="byName">

    <bean id="propertyPlaceholderConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations" value="classpath*:META-INF/fms-local.properties" />
        <property name="systemPropertiesModeName">
            <value>SYSTEM_PROPERTIES_MODE_OVERRIDE</value>
        </property>
    </bean>

    <!-- 
    Classpath scanning to load all the service classes
  -->
    <context:component-scan base-package="ca.statcan"
        use-default-filters="false">
        <context:include-filter type="regex" expression="ca\.statcan\.icos.*\.service\..*Service" />
        <context:include-filter type="regex" expression="ca\.statcan\.icos.*\.builders\..*Builder" />
    </context:component-scan>

        <!-- 
    Spring TransactionManager
  -->
    <tx:advice id="txAdvice" transaction-manager="springTransactionManager">
        <tx:attributes>
            <!-- all methods starting with 'get' are read-only -->
            <tx:method name="get*" read-only="true" propagation="SUPPORTS" isolation="DEFAULT"/>
            <tx:method name="find*" read-only="true" propagation="SUPPORTS" isolation="DEFAULT"/>
            <!-- other methods use the default transaction settings -->
            <tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>

    <!-- 
    AOP Weaving for all Service methods
  -->
    <aop:config proxy-target-class="true">
        <aop:pointcut id="icosServiceMethods" expression="execution(* ca.statcan.icos..*.service.*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="icosServiceMethods" />
    </aop:config>

</beans>

Custom ContextLoaderListener

public class EmbeddedContextLoaderListener extends ContextLoaderListener {

    @Override
    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        GenericWebApplicationContext context = new GenericWebApplicationContext(sc);
        context.setParent(ApplicationContextProvider.getApplicationContext());
        return context;
    }

    @Override
    protected ApplicationContext loadParentContext(ServletContext servletContext) {
        return ApplicationContextProvider.getApplicationContext();
    }
}

Modified Embedded Tomcat Wrapper

public class EmbeddedTomcat {
    /** Log4j logger for this class. */
    @SuppressWarnings("unused")
    private static final Logger LOG = LoggerFactory.getLogger(EmbeddedTomcat.class);

    private Tomcat tomcat;

    public void start() {
        try {
            tomcat = new Tomcat();
            String baseDir = ".";
            tomcat.setPort(8080);
            tomcat.setBaseDir(baseDir);
            tomcat.getHost().setAppBase(baseDir);
            tomcat.getHost().setDeployOnStartup(true);
            tomcat.getHost().setAutoDeploy(true);
            tomcat.enableNaming();

            Context context = tomcat.addContext("/sandbox-web", "src\\main\\webapp");
            Tomcat.initWebappDefaults(context);
            configureSimulatedWebXml(context); 

            LOG.info("Starting tomcat in: " + new File(tomcat.getHost().getAppBase()).getAbsolutePath());

            tomcat.start();
        } catch (LifecycleException e) {
            throw new RuntimeException(e);
        }
    }

    public void stop() {
        try {
            tomcat.stop();
            tomcat.destroy();
            FileUtils.deleteDirectory(new File("work"));
            FileUtils.deleteDirectory(new File("tomcat.8080"));
        } catch (LifecycleException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void deploy(String appName) {
        tomcat.addWebapp(tomcat.getHost(), "/" + appName, "src\\main\\webapp");
    }

    public String getApplicationUrl(String appName) {
        return String.format("http://%s:%d/%s", tomcat.getHost().getName(),
                tomcat.getConnector().getLocalPort(), appName);
    }

    public boolean isRunning() {
        return tomcat != null;
    }

    private void configureSimulatedWebXml(final Context context) {
        // Programmatically configure the web.xml here

        context.setDisplayName("Sandbox Web Application");

        context.addParameter("org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG", "/WEB-INF/tiles-defs.xml,/WEB-INF/tiles-sandbox.xml");

        final FilterDef struts2Filter = new FilterDef();
        struts2Filter.setFilterName("struts2");
        struts2Filter.setFilterClass("org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter");
        struts2Filter.addInitParameter("actionPackages", "ca.statcan.icos.sandbox.web");
        context.addFilterDef(struts2Filter);    

        final FilterMap struts2FilterMapping = new FilterMap();
        struts2FilterMapping.setFilterName("struts2");
        struts2FilterMapping.addURLPattern("/*");
        context.addFilterMap(struts2FilterMapping);

        context.addApplicationListener("org.apache.tiles.web.startup.TilesListener");
        context.addApplicationListener("ca.statcan.icos.sandbox.EmbeddedContextLoaderListener");

        context.addWelcomeFile("index.jsp");
    }
}

Step definitions

public class StepDefs {

    @Autowired
    protected EmployeeEntityService employeeEntityService;

    @Given("^the following divisions exist$")
    public void the_following_divisions_exist(DataTable arg1) throws Throwable {
        final Employee employee = new Employee(3, "Third", "John", null, "613-222-2223");
        employeeEntityService.persistEmployee(employee);
    }

    @Given("^there are no existing surveys$")
    public void there_are_no_existing_surveys() throws Throwable {
    }

    @When("^I register a new survey with the following information$")
    public void I_register_a_new_survey_with_the_following_information(DataTable arg1) throws Throwable {
        Capabilities capabilities = DesiredCapabilities.htmlUnit();
        final HtmlUnitDriver driver = new HtmlUnitDriver(capabilities);

        driver.get("http://localhost:8080/sandbox-web/myFirst");
    }

    @Then("^the surveys are created$")
    public void the_surveys_are_created() throws Throwable {
        // Express the Regexp above with the code you wish you had
        throw new PendingException();
    }

    @Then("^a confirmation message is displayed saying: \"([^\"]*)\"$")
    public void a_confirmation_message_is_displayed_saying(String arg1) throws Throwable {
        // Express the Regexp above with the code you wish you had
        throw new PendingException();
    }
}

Action class

@Results({ @Result(name = "success", location = "myFirst.page", type = "tiles") })
@ParentPackage("default")
@Breadcrumb(labelKey = "ca.statcan.icos.sandbox.firstAction")
@SuppressWarnings("serial")
public class MyFirstAction extends HappyfActionSupport {

    private List<Employee> employees;

    @Autowired
    private EmployeeEntityService employeeEntityService;

    @Override
    public String execute() {
        employees = employeeEntityService.getAllEmployee();
        if (employees.size() == 0) {

            // persist data in memory
            final Employee employee1 = new Employee(1, "First", "John", null, "613-222-2222");
            employeeEntityService.persistEmployee(employee1);

            final Employee employee2 = new Employee(2, "Second", "John", null, "613-222-2223");
            employeeEntityService.persistEmployee(employee2);

            employees = employeeEntityService.getAllEmployee();
        }
        return SUCCESS;
    }

    public List<Employee> getEmployees() {
        return employees;
    }

}

With this, the embedded tomcat starts correctly and all seems to go well until I try to navigate to a web page. In the StepDefs class, the EmployeeEntityService is injected correctly. However, in the Action class, EmployeeEntityservice is not injected (it remains null).

According to my knowledge, I am setting the parent ApplicationContext for the embedded Tomcat correctly. So why isn't the server using the parent context to get the EmployeeEntityService?

like image 269
Jeffrey Cameron Avatar asked Apr 13 '13 13:04

Jeffrey Cameron


People also ask

Where is Tomcat server xml embedded?

xml . As with the other dm Server configuration files, the tomcat-server. xml file is located in the $SERVER_HOME/config directory.

What dependency should you add to your pom xml file if you want your embedded Tomcat to also compile and work with JSP files?

In this pom. xml file, we also include dependencies for embedded Tomcat JSP container: tomcat-jasper , tomcat-jasper-el , and tomcat-jsp-api . This is a simple JSP file to be served by embedded Tomcat server. The application serves a JSP file.

What is embedded Tomcat?

An embedded Tomcat server consists of a single Java web application along with a full Tomcat server distribution, packaged together and compressed into a single JAR, WAR or ZIP file.

What is Tomcat embedded Jasper?

Anyway, the tomcat-embed-jasper is marked as provided , so indicates that you expect the JDK or a container to provide the dependency at runtime. This scope is only available on the compilation and test classpath, and is not transitive.


1 Answers

I was stuck with a similar problem and the solution for using an alternative web.xml is simpler than one would dare to think:

2 lines:

Context webContext = tomcat.addWebapp("/yourContextPath", "/web/app/docroot/");
webContext.getServletContext().setAttribute(Globals.ALT_DD_ATTR, "/path/to/custom/web.xml");

Voila! The magic happens in org.apache.catalina.startup.ContextConfig#getWebXmlSource

Disclaimer: Tested on Tomcat 7.0.42

like image 137
Pavel Lechev Avatar answered Oct 17 '22 09:10

Pavel Lechev