Search code examples
javamaventomcatintellij-idea

Trouble running embedded Tomcat from IntelliJ Idea Community


I'm trying to use the answers here in order to run this example from Oracle in IntelliJ Idea Community Edition. I've created a new project, copied the source code from the example and enabled Maven support in Idea. I'm able to make and run the project but I can't access the service in the browser. Tomcat keeps throwing 404s. Note that the source code and pom.xml file are untouched.

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example.employees</groupId>
    <artifactId>employees-app</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>employees-app Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <properties>
        <tomcat.version>7.0.57</tomcat.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>${tomcat.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-logging-juli</artifactId>
            <version>${tomcat.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>${tomcat.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jasper</artifactId>
            <version>${tomcat.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jasper-el</artifactId>
            <version>${tomcat.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jsp-api</artifactId>
            <version>${tomcat.version}</version>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>
     <build>
        <finalName>employees-app</finalName>
        <resources>
            <resource>
                <directory>src/main/webapp</directory>
                <targetPath>META-INF/resources</targetPath>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <inherited>true</inherited>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>

            </plugin>           
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <finalName>employees-app-${project.version}</finalName>
                    <archive>
                        <manifest>
                            <mainClass>com.example.employees.Main</mainClass>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Using Process Monitor I've been able to determine that Tomcat associated the root of the website (localhost:8080) with MyProject\target\classes. If I place a dummy txt file in there I can access it via the browser (e.g. localhost:8080/test.txt).

The 'classes' directory is not empty. It contains two sub-directories:

  1. com/example/employees/*.class - all the class files
  2. META-INF/resources/[everything in /main/src/webapp in the Oracle example ]

My hunch is that there's something wrong with the folder hierarchy of the output. I'm not sure what or how to fix it. Any ideas?


Solution

  • I figured it out.

    As the linked post indicates, it's best to use Heroku's launcher and pom.xml file.

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.heroku.sample</groupId>
      <artifactId>embeddedTomcatSample</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>jar</packaging>
      <name>embeddedTomcatSample Maven Webapp</name>
      <url>http://maven.apache.org</url>
      <properties>
        <tomcat.version>8.0.28</tomcat.version>
      </properties>
      <dependencies>
        ...tomcat & other deps...
      </dependencies>
      <build>
        <finalName>embeddedTomcatSample</finalName>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>appassembler-maven-plugin</artifactId>
                <version>1.1.1</version>
                <configuration>
                    <assembleDirectory>target</assembleDirectory>
                    <programs>
                        <program>
                            <mainClass>launch.Main</mainClass>
                            <name>webapp</name>
                        </program>
                    </programs>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>assemble</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
      </build>
    </project>
    

    This launcher function does most of the magic by programatically configuring Tomcat to run inside the project's directory.

    public static void main(String[] args) throws Exception {
    
            String webappDirLocation = "src/main/webapp/";
            Tomcat tomcat = new Tomcat();
    
            //The port that we should run on can be set into an environment variable
            //Look for that variable and default to 8080 if it isn't there.
            String webPort = System.getenv("PORT");
            if(webPort == null || webPort.isEmpty()) {
                webPort = "8080";
            }
    
            tomcat.setPort(Integer.valueOf(webPort));
    
            StandardContext ctx = (StandardContext) tomcat.addWebapp("/", new File(webappDirLocation).getAbsolutePath());
            System.out.println("configuring app with basedir: " + new File("./" + webappDirLocation).getAbsolutePath());
    
            // Declare an alternative location for your "WEB-INF/classes" dir
            // Servlet 3.0 annotation will work
            File additionWebInfClasses = new File("target/classes");
            WebResourceRoot resources = new StandardRoot(ctx);
            resources.addPreResources(new DirResourceSet(resources, "/WEB-INF/classes",
                    additionWebInfClasses.getAbsolutePath(), "/"));
            ctx.setResources(resources);
    
            tomcat.start();
            tomcat.getServer().await();
        }
    

    I couldn't figure out how to programatically set the path to Tomcat's conf folder but I was able to overcome this thanks to this answer. Under normal circumstances, Tomcat takes care of setting up the initial context. As a result, the environment variables in a webapp's context.xml file end up in the initial context. Fortunately, it is possible to replicate this via a custom InitialContextFactory.