JavaSE: First Simple Module Exercise A Basic Introduction to

advertisement

JavaSE: First Simple Module Exercise

A Basic Introduction to

JavaSE and Maven Modules

Revision: v2014-08-25

Built on: 2014-12-17 02:49 EST

Copyright © 2014 jim stafford (jim.stafford@jhu.edu)

This document contains an introductory exercise for building a Maven-based JavaSE project that is self-contained. The exercise takes a building-block approach that demystifies some of the

Maven build concepts by breaking down many core concepts into files, directories, and individual commands prior to introducing the overall framework and its integration with an IDE.

Purpose ............................................................................................................................ v

1. Goals .................................................................................................................... v

2. Objectives ............................................................................................................. v

1. Develop and Test Module using Command Line Tools (OPTIONAL!) ........................... 1

1.1. Summary ............................................................................................................ 4

2. Automate Build and Testing with Ant (OPTIONAL!) .................................................... 5

2.1. Summary .......................................................................................................... 12

3. Adding Logging ......................................................................................................... 13

3.1. Summary .......................................................................................................... 17

4. Creating Portable and Repeatable Project Builds with Maven ................................... 19

4.1. Summary .......................................................................................................... 30

5. Leverage IDE using Eclipse ....................................................................................... 31

5.1. Import a project into Eclipse .............................................................................. 31

5.2. Setup Eclipse to be able to execute Maven project goals ..................................... 34

5.3. Setup environment to enable interactive debugging ............................................. 37

5.4. Summary .......................................................................................................... 41

iii

iv

Purpose

1. Goals

• Identify the core use cases required to develop a Java Archive (JAR) module

• Demonstrate how Maven fits within the development of a JAR module

• Demonstrate how Maven integrates with a sample IDE

2. Objectives

At the completion of this topic, the student shall be able to:

• Create a module with directory structure and files to build a Java Archive (JAR)

• Create a Java class for inclusion in the JAR

• Create a unit test for the Java class

• Automate the build using Maven

• Import the Maven module into an IDE for development

• Use the IDE to interactively debug the Java class and unit test

Note

Some of the parts of this exercise are marked OPTIONAL! There is no need to physically perform the details of these steps if you are already familiar with the concepts presented. Skim the material and advance to the parts you are not familiar with.

v

vi

Chapter 1.

Develop and Test Module using

Command Line Tools (OPTIONAL!)

In this chapter you will be introduced to a standard module file structure that contains a class we intend to use in production and a unit test to verify the functionality of the production class. You will be asked to form the directory structure of files and execute the commands required to build and run the unit test.

Warning

This chapter is optional!!! It contains many tedious steps that are somewhat shellspecific. The intent is to simply introduce the raw data structure and actions that need to take place and then to later automate all of this through Maven. If you wish to just skim the steps -- please do. Please do not waste time trying to port these bash shell commands to your native shell.

Note

This part requires junit.jar. These should have been downloaded for you when you built the class examples and can be located in $M2_REPO/junit/junit/(version)/.

Where M2_REPO is HOME/.m2/repository or the location you have specified in the localRepository element of HOME/.m2/settings.xml.

1. Set a few logical variables to represent root directories. For the purposes of the follow-on steps,

PROJECT_BASEDIR is the root directory for this exercise. In the example below, the user has chosen a directory of $HOME/proj/784/exercises to be the root directory for all class exercises and named the root directory for this project "ex1". An alternative for CLASS_HOME might be c:/jhu/784.

export CLASS_HOME=$HOME/proj/784 export PROJECT_BASEDIR=$CLASS_HOME/exercises/ex1 mkdir -p $PROJECT_BASEDIR cd $PROJECT_BASEDIR

2. Create project directory structure. In this example, the developer used $HOME/proj/784 for all work in this class.

$PROJECT_BASEDIR

+---/src/main/java/myorg/mypackage/ex1

+---/src/test/java/myorg/mypackage/ex1

+---/target/classes

+---/target/test-classes

+---/target/test-reports

1

Chapter 1. Develop and Test M...

mkdir -p src/main/java/myorg/mypackage/ex1 mkdir -p src/test/java/myorg/mypackage/ex1 mkdir -p target/classes mkdir -p target/test-classes mkdir -p target/test-reports

3. Add the following Java implementation class to PROJECT_BASEDIR/src/main/java/myorg/ mypackage/ex1/App.java

package myorg.mypackage.ex1; public class App { public int returnOne() {

System.out.println( "Here's One!" ); return 1 ;

} public static void main( String[] args ) {

System.out.println( "Hello World!" );

}

}

4. Add the following Java test class to PROJECT_BASEDIR/src/test/java/myorg/mypackage/ex1/

AppTest.java

package myorg.mypackage.ex1; import static org.junit.Assert.*; import org.junit.Test;

/**

* Unit test for simple App.

*/ public class AppTest {

@Test public void testApp() {

System.out.println( "testApp" );

App app = new App();

}

assertTrue( "app didn't return 1" , app.returnOne() == 1 );

}

Note

Make sure you put AppTest.java in the src/test tree.

5. Compile the application and place it in target/ex1.jar. The compiled classes will go in target/ classes.

2

javac src/main/java/myorg/mypackage/ex1/App.java -d target/classes jar cvf target/ex1.jar -C target/classes .

jar tf target/ex1.jar

$ javac src/main/java/myorg/mypackage/ex1/App.java -d target/classes

$ jar cvf target/ex1.jar -C target/classes .

added manifest adding: myorg/(in = 0) (out= 0)(stored 0%) adding: myorg/mypackage/(in = 0) (out= 0)(stored 0%) adding: myorg/mypackage/ex1/(in = 0) (out= 0)(stored 0%) adding: myorg/mypackage/ex1/App.class(in = 519) (out= 350)(deflated 32%)

$ jar tf target/ex1.jar

META-INF/

META-INF/MANIFEST.MF

myorg/ myorg/mypackage/ myorg/mypackage/ex1/ myorg/mypackage/ex1/App.class

6. Compile the JUnit test and place the compiled tests in target/test-classes.

export JUNIT_JAR="$HOME/.m2/repository/junit/junit/4.10/junit-4.10.jar" javac -classpath "target/ex1.jar:$JUNIT_JAR" src/test/java/myorg/mypackage/ex1/AppTest.java -d target/test-classes

7. Verify you have your "production" class from src/main compiled into target/classes, your unit test class from src/test compiled into target/test-classes, and the Java archive with thr production class in target directory.

target/

+---classes/myorg/mypackage/ex1/App.class

+---test-classes/myorg/mypackage/ex1/AppTest.class

+---test-reports

+---ex1.jar

8. Run the JUnit test framework.

java -classpath

myorg.mypackage.ex1.AppTest

"target/ex1.jar:$JUNIT_JAR:target/test-classes" org.junit.runner.JUnitCore

JUnit version 4.10

.testApp

Here's One!

Time: 0.005

OK (1 test)

9. Change add/remove a test that will fail, re-compile the test class and re-run.

//AppTest.java

3

4

Chapter 1. Develop and Test M...

@Test public void testFail() {

System.out.println( "testFail" );

App app = new App();

assertTrue( "app didn't return 0" , app.returnOne() == 0 );

} javac -classpath "target/ex1.jar:$JUNIT_JAR" src/test/java/myorg/mypackage/ex1/AppTest.java -d target/test-classes java -classpath "target/ex1.jar:$JUNIT_JAR:target/test-classes" org.junit.runner.JUnitCore

myorg.mypackage.ex1.AppTest

JUnit version 4.10

.testApp

Here's One!

.testFail

Here's One!

E

Time: 0.007

There was 1 failure:

1) testFail(myorg.mypackage.ex1.AppTest) java.lang.AssertionError: app didn't return 0

at org.junit.Assert.fail(Assert.java:93)

at org.junit.Assert.assertTrue(Assert.java:43)

at myorg.mypackage.ex1.AppTest.testFail(AppTest.java:26)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

...

at org.junit.runner.JUnitCore.main(JUnitCore.java:45)

FAILURES!!!

Tests run: 2, Failures: 1

1.1. Summary

In this chapter of the exercise you setup, built, and tested a sample project with only command-line commands. You did this to help show what higher level tools will need to do as well. Even though the command line provides clarity, it doesn't scale and shell scripts aren't generally portable and optimized (wrong tool for the job). Hopefully, after going through this, you have an understanding of the low level structure and usecases and are now interested adding a build environment.

Chapter 2.

Automate Build and Testing with Ant

(OPTIONAL!)

This chapter demonstrates the basics of automating the manual steps in the previous chapter using the Apache Ant build tool. If you just skim thru this step, please be sure to take note of how everything gets explicitly defined in Ant. There are not many rules of the road and standard defaults to live by. That will be a big contrast when working with Maven.

Note

All course examples and projects submitted will use Maven. Ant will be used to wrap command lines for Java SE clients executed outside the normal build environment. However, this exercise shows Ant only being used as part of the artifact build and test environment as a stepping stone to understanding some of the basic build and test concepts within Maven.

Note

If you do not have Ant installed on your system, it can be from http://ant.apache.org/

Warning

This chapter is optional!!! It contains many tedious steps to setup a module build using the Ant build tool -- which will not be part of class. It is presented here as an example option to building the module with shell scripts. If you wish to just skim the steps -- please do. Please do not waste time trying to get Ant to build your Java modules for this class.

1. Create a build.properties file in PROJECT_BASEDIR. This will be used to define any nonportable property values. Place the most non-portable base variables (.e.g, M2_REPO location) towards the top and build lower-level paths from them. This makes the scripts much easier to port to another environment. If you still have your maven repository in your $HOME directory, you can make use of ${user.home} environment variable rather than a hard-coded path.

#ex1 build.properties

#M2_REPO=c:/jhu/repository

M2_REPO=${user.home}/.m2/repository junit.classpath=${M2_REPO}/junit/junit/4.10/junit-4.10.jar

2. Create a build.xml file in PROJECT_BASEDIR. Note the following key elements.

• project - a required root for build.xml files

• name - not significant, but helpful

• default - the target to run if none is supplied on command line

5

Chapter 2. Automate Build and...

• basedir - specifies current directory for all tasks

• property - defines an immutable name/value

• file - imports declarations from a file; in this case build.properties created earlier

• name/value - specifies a property within the script

• target - defines an entry point into the build.xml script. It hosts one or more tasks.

• name - defines name of target, which can be supplied on command line.

• echo - a useful Ant task to printout status and debug information. See Ant docs [http:// ant.apache.org/manual/CoreTasks/echo.html] for more information.

<?xml version="1.0" encoding="utf-8" ?>

<!-- ex1 build.xml

-->

< project name = "ex1" default = "" basedir = "." >

< property file = "build.properties" />

< property name = "artifactId" value = "ex1" />

< property name = "src.dir" value = "${basedir}/src" />

< property name = "build.dir" value = "${basedir}/target" />

< target name = "echo" >

< echo > basedir=${basedir} </ echo >

< echo > artifactId=${artifactId} </ echo >

< echo > src.dir=${src.dir} </ echo >

< echo > build.dir=${build.dir} </ echo >

< echo > junit.classpath=${junit.classpath} </ echo >

</ target >

</ project >

3. Sanity check your build.xml and build.properties file with the echo target.

$ ant echo

Buildfile: /home/jcstaff/proj/784/exercises/ex1/build.xml

echo:

[echo] basedir=/home/jcstaff/proj/784/exercises/ex1

[echo] artifactId=ex1

[echo] src.dir=/home/jcstaff/proj/784/exercises/ex1/src

[echo] build.dir=/home/jcstaff/proj/784/exercises/ex1/target

[echo] junit.classpath=/home/jcstaff/.m2/repository/junit/junit/4.10/junit-4.10.jar

BUILD SUCCESSFUL

Total time: 0 seconds

4. Add the "package" target to compile and archive your /src/main classes. Note the following tasks in this target.

• mkdir - creates a directory. See Ant docs [http://ant.apache.org/manual/CoreTasks/ mkdir.html] for more infomation.

• javac - compiles java sources files. See Ant docs [http://ant.apache.org/manual/CoreTasks/ javac.html] for more information. Note that we are making sure we get JavaSE 7 classes compiled.

6

• * jar - builds a java archive. See Ant Docs [http://ant.apache.org/manual/CoreTasks/jar.html] for more information.

< target name = "package" >

< mkdir dir = "${build.dir}/classes" />

< javac srcdir = "${src.dir}/main/java" destdir = "${build.dir}/classes" debug = "true" source = "1.7" target = "1.7" includeantruntime = "false" >

< classpath >

</ classpath >

</ javac >

< jar destfile = "${build.dir}/${artifactId}.jar" >

< fileset dir = "${build.dir}/classes" />

</ jar >

</ target >

5. Execute the "package" target just added. This should compile the production class from src/ main into target/classes and build a Java archive with the production class in target/.

$ rm -rf target/; ant package

Buildfile: /home/jcstaff/proj/784/exercises/ex1/build.xml

package:

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/classes

[javac] Compiling 1 source file to /home/jcstaff/proj/784/exercises/ex1/target/classes

[jar] Building jar: /home/jcstaff/proj/784/exercises/ex1/target/ex1.jar

BUILD SUCCESSFUL

Total time: 2 seconds

Note

You may get the following error when you execute the javac task. If so, export

JAVA_HOME=(path to JDK_HOME) on your system to provide Ant a reference to a JDK instance.

build.xml:26: Unable to find a javac compiler; com.sun.tools.javac.Main is not on the classpath.

Perhaps JAVA_HOME does not point to the JDK.

It is currently set to ".../jre"

$ find . -type f

./src/main/java/myorg/mypackage/ex1/App.java

./src/test/java/myorg/mypackage/ex1/AppTest.java

./build.properties

./build.xml

./target/classes/myorg/mypackage/ex1/App.class

7

Chapter 2. Automate Build and...

./target/ex1.jar

6. Add the "test" target to compile your /src/test classes. Make this the default target for your build.xml file. Note too that it should depend on the successful completion of the "package" target and include the produced archive in its classpath.

< project name = "ex1" default = "test" basedir = "." >

...

< target name = "test" depends = "package" >

< mkdir dir = "${build.dir}/test-classes" />

< javac srcdir = "${src.dir}/test/java" destdir = "${build.dir}/test-classes" debug = "true" source = "1.7" target = "1.7" includeantruntime = "false" >

< classpath >

< pathelement location = "${build.dir}/${artifactId}.jar" />

< pathelement path = "${junit.classpath}" />

</ classpath >

</ javac >

</ target >

7. Execute the new "test" target after clearing out the contents of the target directory. Note that the target directory gets automatically re-populated with the results of the "compile" target and augmented with the test class from src/test compiled into target/test-classes.

$ rm -rf target/; ant

Buildfile: /home/jcstaff/proj/784/exercises/ex1/build.xml

package:

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/classes

[javac] Compiling 1 source file to /home/jcstaff/proj/784/exercises/ex1/target/classes

[jar] Building jar: /home/jcstaff/proj/784/exercises/ex1/target/ex1.jar

test:

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/test-classes

[javac] Compiling 1 source file to /home/jcstaff/proj/784/exercises/ex1/target/test-classes

BUILD SUCCESSFUL

Total time: 3 seconds

> find . -type f

./src/main/java/myorg/mypackage/ex1/App.java

./src/test/java/myorg/mypackage/ex1/AppTest.java

./build.properties

./build.xml

./target/classes/myorg/mypackage/ex1/App.class

./target/ex1.jar

./target/test-classes/myorg/mypackage/ex1/AppTest.class

8

8. Add the junit task to the test target. The junit task is being configured to run in batch mode and write a TXT and XML reports to the target/test-reports directory. See {{{http://ant.apache.org/ manual/OptionalTasks/junit.html}Ant docs}} for more details on the junit task. Make special note of the following:

• printsummary - produce a short summary to standard out showing the number of tests run and a count of errors, etc.

• fork - since Ant runs in a JVM, any time you run a task that requires a custom classpath, it is usually required that it be forked into a separate process (with its own classpath).

• batchtest - run all tests found and write results of each test into the test-reports directory.

• formatter - write a text and XML report of results

< mkdir dir = "${build.dir}/test-reports" />

< junit printsummary = "true" fork = "true" >

< classpath >

< pathelement path = "${junit.classpath}" />

< pathelement location = "${build.dir}/${artifactId}.jar" />

< pathelement location = "${build.dir}/test-classes" />

</ classpath >

< batchtest fork = "true" todir = "${build.dir}/test-reports" >

< fileset dir = "${build.dir}/test-classes" >

< include name = "**/*Test*.class" />

</ fileset >

</ batchtest >

< formatter type = "plain" />

< formatter type = "xml" />

</ junit >

Note

The last time I sanity checked this exercise I got the common error below. I corrected the issue by downloading a full installation from the Ant website and exporting my ANT_HOME to the root of that installation. (export ANT_HOME=/ opt/apache-ant-1.9.4) and adding $ANT_HOME/bin to the PATH (export PATH=

$ANT_HOME/bin:$PATH) ANT_HOME is required for Ant to locate the junit task.

BUILD FAILED

/home/jcstaff/proj/784/exercises/ex1/build.xml:57: Problem: failed to create task or type junit

Cause: the class org.apache.tools.ant.taskdefs.optional.junit.JUnitTask was not found.

This looks like one of Ant's optional components.

Action: Check that the appropriate optional JAR exists in

-/usr/share/ant/lib

-/home/jcstaff/.ant/lib

-a directory added on the command line with the -lib argument

Do not panic, this is a common problem.

The commonest cause is a missing JAR.

9

Chapter 2. Automate Build and...

This is not a bug; it is a configuration problem

9. Execute the updated "test" target with the JUnit test.

$ rm -rf target; ant package:

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/classes

[javac] Compiling 1 source file to /home/jcstaff/proj/784/exercises/ex1/target/classes

[jar] Building jar: /home/jcstaff/proj/784/exercises/ex1/target/ex1.jar

test:

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/test-classes

[javac] Compiling 1 source file to /home/jcstaff/proj/784/exercises/ex1/target/test-classes

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/test-reports

[junit] Running myorg.mypackage.ex1.AppTest

[junit] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 15.143 sec

[junit] Test myorg.mypackage.ex1.AppTest FAILED

BUILD SUCCESSFUL

Total time: 17 seconds

$ find . -type f

./src/main/java/myorg/mypackage/ex1/App.java

./src/test/java/myorg/mypackage/ex1/AppTest.java

./build.properties

./build.xml

./target/classes/myorg/mypackage/ex1/App.class

./target/ex1.jar

./target/test-classes/myorg/mypackage/ex1/AppTest.class

./target/test-reports/TEST-myorg.mypackage.ex1.AppTest.txt

./target/test-reports/TEST-myorg.mypackage.ex1.AppTest.xml

Note

Note the 17 seconds it took to run/complete the test seems excessive. I was able to speed that up to 0.001 sec by commenting out the XML report option

(which we will not use in this exercise).

10.Test output of each test is in the TXT and XML reports.

$ more target/test-reports/TEST-myorg.mypackage.ex1.AppTest.txt

Testsuite: myorg.mypackage.ex1.AppTest

Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 15.246 sec

------------- Standard Output --------------testApp

Here's One!

testFail

Here's One!

------------- ---------------- ---------------

Testcase: testApp took 0.007 sec

Testcase: testFail took 0.022 sec

10

FAILED app didn't return 0 junit.framework.AssertionFailedError: app didn't return 0

at myorg.mypackage.ex1.AppTest.testFail(AppTest.java:26)

11.Add a clean target to the build.xml file to delete built artifacts. See Ant docs [http:// ant.apache.org/manual/CoreTasks/delete.html] for details on the delete task.

< target name = "clean" >

< delete dir = "${build.dir}" />

</ target >

12.Re-run and use the new "clean" target you just added.

$ ant clean test

Buildfile: /home/jcstaff/proj/784/exercises/ex1/build.xml

clean:

[delete] Deleting directory /home/jcstaff/proj/784/exercises/ex1/target package:

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/classes

[javac] Compiling 1 source file to /home/jcstaff/proj/784/exercises/ex1/target/classes

[jar] Building jar: /home/jcstaff/proj/784/exercises/ex1/target/ex1.jar

test:

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/test-classes

[javac] Compiling 1 source file to /home/jcstaff/proj/784/exercises/ex1/target/test-classes

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/test-reports

[junit] Running myorg.mypackage.ex1.AppTest

[junit] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 15.123 sec

[junit] Test myorg.mypackage.ex1.AppTest FAILED

BUILD SUCCESSFUL

Total time: 17 seconds

13.Comment out the bogus testFail and rerun.

$ cat src/test/java/myorg/mypackage/ex1/AppTest.java

...

//@Test

public void testFail() {

14.

$ ant clean test

Buildfile: /home/jcstaff/proj/784/exercises/ex1/build.xml

clean:

[delete] Deleting directory /home/jcstaff/proj/784/exercises/ex1/target package:

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/classes

[javac] Compiling 1 source file to /home/jcstaff/proj/784/exercises/ex1/target/classes

11

Chapter 2. Automate Build and...

[jar] Building jar: /home/jcstaff/proj/784/exercises/ex1/target/ex1.jar

test:

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/test-classes

[javac] Compiling 1 source file to /home/jcstaff/proj/784/exercises/ex1/target/test-classes

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/test-reports

[junit] Running myorg.mypackage.ex1.AppTest

[junit] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 15.161 sec

BUILD SUCCESSFUL

Total time: 17 seconds

2.1. Summary

In this chapter you performed many of the same action you did in the previous chapter except that you implemented in a portable build tool called Ant. Ant is very powerful and open-ended.

You were shown these same steps in Ant as an intermediate towards Maven. Ant, being openended, does not follow many conventions. Everything must be explicitely specified. You will find that to be in significant contrast with Maven. Ant is a "build tool" and Maven is a "build system" with conventions/rules.

12

Chapter 3.

Adding Logging

In this chapter we will refine the use of print and debug statements by using a "logger". By adopting a logger into your production and test code you can avoid print statements to stdout/stderr and be able to re-direct them to log files, databases, messaging topics etc. There are several to choose from (Java's built-in logger, Commons logging API, SLF's logging API, and log4j to name a few).

This exercise just happens to use commons-logging API and log4j implementation.

1. Change the System.out() calls in App and AppTest from Part A to use commons logging

API. The commons-logging Javadoc [http://jakarta.apache.org/commons/logging/apidocs/ index.html] and guide [http://jakarta.apache.org/commons/logging/guide.html] will be helpful in understanding this interface. The guide goes into some details about log4j configuration as well.

package myorg.mypackage.ex1; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class App { private static Log log = LogFactory.getLog(App.

class ); public int returnOne() {

//System.out.println( "Here's One!" );

log.debug( "Here's One!" ); return 1 ;

}

} public static void main( String[] args ) {

//System.out.println( "Hello World!" );

log.info( "Hello World!" );

} package myorg.mypackage.ex1;

...

import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class AppTest { private static Log log = LogFactory.getLog(AppTest.

class );

...

@Test public void testApp() {

//System.out.println("testApp");

log.info( "testApp" );

App app = new App();

assertTrue( "app didn't return 1" , app.returnOne() == 1 );

13

Chapter 3. Adding Logging

}

}

2. Add a log4j.xml configuration file to the directory structure. Place this file in src/test/resources/ log4j.xml. This file is used to control logging output. Refer to the log4j documentation page

[http://logging.apache.org/log4j/docs/documentation.html] for possible information on how to configure and use log4j. However, they sell a commercial text; so its hard to find a good, detailed online reference that goes through all options. It doesn't matter whether you use a log4j.xml

format or log4j.properties format. However, their quick intro [http://logging.apache.org/log4j/ docs/manual.html] uses the property file format.

<?xml version="1.0" encoding="UTF-8"?>

< !

DOCTYPE log4j:configuration SYSTEM "log4j.dtd" >

< log4j:configuration xmlns:log4j = "http://jakarta.apache.org/log4j/" debug = "false" >

< appender name = "CONSOLE" class = "org.apache.log4j.ConsoleAppender" >

< param name = "Target" value = "System.out" />

< layout class = "org.apache.log4j.PatternLayout" >

< param name = "ConversionPattern" value = "%-5p %d{dd-MM HH:mm:ss,SSS} (%F:%M:%L) -%m%n" />

</ layout >

</ appender >

< appender name = "logfile" class = "org.apache.log4j.RollingFileAppender" >

< param name = "File" value = "target/log4j-out.txt" />

< param name = "Append" value = "false" />

< param name = "MaxFileSize" value = "100KB" />

< param name = "MaxBackupIndex" value = "1" />

< layout class = "org.apache.log4j.PatternLayout" >

< param name = "ConversionPattern" value = "%-5p %d{dd-MM HH:mm:ss,SSS} [%c] (%F:%M:%L) -%m%n" />

</ layout >

</ appender >

< logger name = "myorg.mypackage" >

< level value = "debug" />

< appender-ref ref = "logfile" />

</ logger >

< root >

< priority value = "info" />

< appender-ref ref = "CONSOLE" />

</ root >

</ log4j:configuration >

14

Note

The log4j.xml is placed in the JVM classpath; where log4j will locate it by default.

However, it should not be placed in with the main classes (ex1.jar). Placing it in a our JAR file would polute the application assembler and deployer's job of specifying the correct configuration file at runtime. Our test classes and resources are not a part of follow-on deployment.

3. Add the commons-logging.jar to the compile classpaths and the commons-logging.jar and log4j.jar to the runtime classpath. Also add an additional task to copy the log4j.xml file into target/test-classes so that it is seen by the classloader as a resource. Realize that your classes have no compilation dependencies on log4j. Log4j is only used if it is located at runtime.

# ex1 build.properties

commons-logging.classpath=${M2_REPO}/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar

log4j.classpath=${M2_REPO}/log4j/log4j/1.2.13/log4j-1.2.13.jar

< target name = "echo" >

...

< echo > commons-logging.classpath=${commons-logging.classpath} </ echo >

< echo > log4j.classpath=${log4j.classpath} </ echo >

</ target >

< javac srcdir = "${src.dir}/main/java" destdir = "${build.dir}/classes" debug = "true" source = "1.7" target = "1.7" includeantruntime = "false" >

< classpath >

< pathelement path = "${commons-logging.classpath}" />

</ classpath >

</ javac >

< javac srcdir = "${src.dir}/test/java" destdir = "${build.dir}/test-classes" debug = "true" source = "1.7" target = "1.7" includeantruntime = "false" >

< classpath >

< pathelement location = "${build.dir}/${artifactId}.jar" />

< pathelement path = "${junit.classpath}" />

< pathelement path = "${commons-logging.classpath}" />

</ classpath >

</ javac >

< copy todir = "${build.dir}/test-classes" >

< fileset dir = "${src.dir}/test/resources" />

</ copy >

15

Chapter 3. Adding Logging

< junit printsummary = "true" fork = "true" >

< classpath >

< pathelement path = "${junit.classpath}" />

< pathelement location = "${build.dir}/${artifactId}.jar" />

< pathelement location = "${build.dir}/test-classes" />

< pathelement path = "${commons-logging.classpath}" />

< pathelement path = "${log4j.classpath}" />

</ classpath >

...

4. Test application and inspect reports. All loggers inherit from the root logger and may only extend its definition; not limit it. Notice that the root logger's priority filter "info" value allows log.info()

(warning and fatal) messages to printed to the console. The myorg.mypackage logger's level filter allows log.debug() messages from the myorg.mypackage.* classes to appear in both the console and logfile. This means that any Java classes not in our package hierarchy will only have INFO or higher priority messages logged.

$ ant clean test

Buildfile: /home/jcstaff/proj/784/exercises/ex1/build.xml

clean:

[delete] Deleting directory /home/jcstaff/proj/784/exercises/ex1/target package:

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/classes

[javac] Compiling 1 source file to /home/jcstaff/proj/784/exercises/ex1/target/classes

[jar] Building jar: /home/jcstaff/proj/784/exercises/ex1/target/ex1.jar

test:

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/test-classes

[javac] Compiling 1 source file to /home/jcstaff/proj/784/exercises/ex1/target/test-classes

[copy] Copying 1 file to /home/jcstaff/proj/784/exercises/ex1/target/test-classes

[mkdir] Created dir: /home/jcstaff/proj/784/exercises/ex1/target/test-reports

[junit] Running myorg.mypackage.ex1.AppTest

[junit] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 15.127 sec

BUILD SUCCESSFUL

Total time: 17 seconds

You won't see the output come to stdout when using Ant, but you can locate all output in the

FILE logger output defined to be in target/log4j-out.txt. This behavior will get a little better under

Maven.

$ more target/log4j-out.txt

INFO 26-08 22:59:23,357 [myorg.mypackage.ex1.AppTest] (AppTest.java:testApp:17) -testApp

DEBUG 26-08 22:59:23,361 [myorg.mypackage.ex1.App] (App.java:returnOne:11) -Here's One!

Your project structure should look like the following at this point.

> find . -type f

./src/main/java/myorg/mypackage/ex1/App.java

16

Summary

./src/test/java/myorg/mypackage/ex1/AppTest.java

./src/test/resources/log4j.xml

./build.properties

./build.xml

./target/classes/myorg/mypackage/ex1/App.class

./target/ex1.jar

./target/test-classes/myorg/mypackage/ex1/AppTest.class

./target/test-classes/log4j.xml

./target/test-reports/TEST-myorg.mypackage.ex1.AppTest.txt

./target/test-reports/TEST-myorg.mypackage.ex1.AppTest.xml

./target/log4j-out.txt

5. Change the logging level so that only the App class performs logs to the logfile. By extending the logger name specification all the way to the class, we further limit which classes apply to this logger.

< logger name = "myorg.mypackage.ex1.App" >

< level value = "debug" />

< appender-ref ref = "logfile" />

</ logger >

After re-running the build you should notice the DEBUG for only the App is included because of the change we made to the logger outside the code.

$ more target/log4j-out.txt

DEBUG 26-08 23:07:04,809 [myorg.mypackage.ex1.App] (App.java:returnOne:11) -Here's One!

6. Repeat after me. "I will never use System.out.println() in this class." Doing so will make it difficult for your deployed components to have their logs controlled and accessible as it is instantiated in unit testing, integration testing, and deployment environments.

3.1. Summary

In this chapter we added some sophistication to the production class and its unit test which required additional compile-time and runtime resources. Please note that we added only a dependency on commons-logging to the javac compiler task because log4j was never directly referenced in the source code. We added a runtime dependency on log4j in order to configure a logger implementation during the execution of tests. The distinction of resources required for compile-time, test, and runtime required will become even more important when using Maven.

Use the explicit classpaths we created here to help you understand Maven dependency scope when we get there.

17

18

Chapter 4.

Creating Portable and Repeatable

Project Builds with Maven

In this chapter you will automate the build using Maven by defining a simple Maven project definition that will go with your project tree. In the previous chapters you worked with a reasonable project tree that could have looked different in a number of ways and could have been accounted for by different path constructs. However, why be different? The project tree we put together that accounted for production classes, test classes, resource files, archives, unit tests, test reports, etc.

follows Maven's standard build tree almost exactly (with the exception of the name of the target/ test-reports directory). We will be able to add a Maven project definition without much effort.

Tip

The Maven community has a tremendous amount of documentation, examples, and on-line discussions. This course has many examples that are more specific for the work you will be actively performing. Many of these resources are a quick google search away but know that I go out of my way to make sure you spend as much time as possible on design and JavaEE aspects in class. If you are stuck on Maven -- ask. I know what you are trying to do and can likely point you to an example that is relevant to what you are doing in class. If you are still stuck on

Maven issues -- send it to me. I will fix it personally. There is nothing more irritating for you than to be fighting with the builds when you want to be spending more time understanding, designing, trying, and mastering the product of what is being built.

Note

Using Maven requires only an initial download and installation. Plugins and dependencies will be downloaded from remote repositories as needed.

Connectivity to the internet is required until all dependencies have been satisfied.

Note

Maven will automatically go out and download any missing dependencies and recursively download what they depend upon. If you are running Maven for the first time, this could result in a significant amount of downloading and may encounter an occasional connection failure with repositories. Once a non-SNAPSHOT version is downloaded (e.g., 1.3), Maven will not re-attempt to download it. Maven will, however, go out and check various resources to stay in sync. If you know you already have everything you need, you can run in off-line mode using the "-o" flag on the command line or its equivalent entry within the settings.xml file. This can save you seconds of build time when disconnected from the Internet.

19

Chapter 4. Creating Portable ...

1. Create a pom.xml file in project basedir. This will be used to define your entire project. Refer to the Maven POM Reference [http://maven.apache.org/ref/current/maven-model/maven.html] for details about each element.

• modelVersion - yes; its required

• groupId - just as it sounds, this value is used to group related artifacts. groupId is a hierarchical value and the individual names are used to form a directory structure in the

Maven repository (e.g., artifacts in the myorg.myproject.foo groupId will be located below the

HOME/.m2/repository/myorg/myproject/foo directory).

• version - Maven has a strong versioning system and versions appended with the word

SNAPSHOT are handled differently. Projects with a version ending in -SNAPSHOT are thought to be in constant change, with no official release yet available. Projects with a version lacking the -SNAPSHOT ending are meant to be an official release, with no other variants available with the same tag.

• dependency.scope - this is used to define the scope the dependency gets applied. It defines the visibility within the project for the dependency and whether it is carried along with the module through transitive dependency analysis. With open-source software, a typical JavaEE application could have 10s to 100s of individual modules it dependends upon and the proper use of transitive dependencies makes this manageable.

• scope=compile is the default and is used to describe artifacts that the src/main directory depends upon and will also be visible by classes in src/test. These dependency artifacts will be brought along with the module when transitive dependencies are evaluated.

• scope=test is used to define artifacts which src/test depends upon. These will be made available during testing, but will not be visible to classes in src/main and will not be considered a dependency for downstream users of the module. Consult the maven documentation for other scopes, but one other that is commonly used in class is scope=provided.

• scope=provided is similar to scope=compile in that the src/main tree can see it, however like scope=test, it is not carried forward. Each downstream module is required to know about the dependency and provide a replacement. This is common when using JavaEE

APIs that have been packaged by different vendors used by different module developers.

• maven-compiler-plugin - this declaration is not necessary for this exercise, but like our Ant script, specify the Java version to make sure we get what we need.

• properties.project.build.sourceEncoding - this defines the default handling of file content for all plugins within a module. The default is platform-specific if left unspecified and we avoid an annoying warning by specifying it.

Note

Although the m2e Eclipse plugin reads the pom dependency and creates a classpath within Eclipse, it does not honor the differences between the different scope values. All dependencies are blended together. The result is that something may compile and run fine within Eclipse and report a missing class when built at the command line. If that happens, check for classes using artifacts

20

that have been brought in as scope=test or for classes incorrectly placed within the src/main tree.

<?xml version="1.0"?>

< project >

< modelVersion > 4.0.0

</ modelVersion >

< groupId > myorg.myproject

</ groupId >

< artifactId > ex1 </ artifactId >

< name > My First Simple Project </ name >

< version > 1.0-SNAPSHOT </ version >

< properties >

< project.build.sourceEncoding

> UTF-8 </ project.build.sourceEncoding

>

</ properties >

< dependencies >

< dependency >

< groupId > commons-logging </ groupId >

< artifactId > commons-logging </ artifactId >

< version > 1.1.1

</ version >

< scope > compile </ scope >

</ dependency >

< dependency >

< groupId > junit </ groupId >

< artifactId > junit </ artifactId >

< version > 4.10

</ version >

< scope > test </ scope >

</ dependency >

< dependency >

< groupId > log4j </ groupId >

< artifactId > log4j </ artifactId >

< version > 1.2.13

</ version >

< scope > test </ scope >

</ dependency >

</ dependencies >

< build >

< plugins >

< plugin >

< groupId > org.apache.maven.plugins

</ groupId >

< artifactId > maven-compiler-plugin </ artifactId >

< version > 3.1

</ version >

< configuration >

< source > 1.7

</ source >

< target > 1.7

</ target >

</ configuration >

</ plugin >

</ plugins >

</ build >

</ project >

21

Chapter 4. Creating Portable ...

Your project tree should look like the following at this point.

> find . -type f

./src/main/java/myorg/mypackage/ex1/App.java

./src/test/java/myorg/mypackage/ex1/AppTest.java

./src/test/resources/log4j.xml

./build.properties

./build.xml

./pom.xml

2. Note that the pom.xml file is not required to have an assigned schema. However, adding one does allow for XML editing tools to better assist in creating a more detailed POM. Replace the project element from above with the following declarations to assign an XML schema.

< 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" >

3. Run the package "phase" and watch the project compile, assemble, and test. Maven has many well-known phases that correspond to the lifecycle of build steps that goes into validating, preparing, building, testing, and deploying artifacts of a module. You can find out more about Maven phases here [http://maven.apache.org/guides/introduction/introduction-to-thelifecycle.html#Lifecycle_Reference] I refer to this page very often.

$ mvn package

[INFO] Scanning for projects...

[INFO]

[INFO] ------------------------------------------------------------------------

[INFO] Building My First Simple Project 1.0-SNAPSHOT

[INFO] ------------------------------------------------------------------------

[INFO]

[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ ex1 ---

[INFO] Using 'UTF-8' encoding to copy filtered resources.

[INFO] skip non existing resourceDirectory /home/jcstaff/proj/784/exercises/ex1/src/main/resources

[INFO]

[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ ex1 ---

[INFO] Changes detected - recompiling the module!

[INFO] Compiling 1 source file to /home/jcstaff/proj/784/exercises/ex1/target/classes

[INFO]

[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ ex1 ---

[INFO] Using 'UTF-8' encoding to copy filtered resources.

[INFO] Copying 1 resource

[INFO]

[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ ex1 ---

[INFO] Changes detected - recompiling the module!

[INFO] Compiling 1 source file to /home/jcstaff/proj/784/exercises/ex1/target/test-classes

[INFO]

[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ ex1 ---

[INFO] Surefire report directory: /home/jcstaff/proj/784/exercises/ex1/target/surefire-reports

-------------------------------------------------------

T E S T S

22

-------------------------------------------------------

Running myorg.mypackage.ex1.AppTest

INFO 27-08 00:15:14,277 (AppTest.java:testApp:17) -testApp

DEBUG 27-08 00:15:14,283 (App.java:returnOne:11) -Here's One!

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.383 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO]

[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ ex1 ---

[INFO] Building jar: /home/jcstaff/proj/784/exercises/ex1/target/ex1-1.0-SNAPSHOT.jar

[INFO] ------------------------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO] ------------------------------------------------------------------------

[INFO] Total time: 5.984 s

[INFO] Finished at: 2014-08-27T00:15:14-04:00

[INFO] Final Memory: 16M/174M

[INFO] ------------------------------------------------------------------------

4.

> find . -type f

./build.xml

./build.properties

./pom.xml

./target/surefire-reports/TEST-myorg.mypackage.ex1.AppTest.xml

./target/surefire-reports/myorg.mypackage.ex1.AppTest.txt

./target/log4j-out.txt

./target/maven-archiver/pom.properties

./target/ex1-1.0-SNAPSHOT.jar

./target/test-classes/myorg/mypackage/ex1/AppTest.class

./target/test-classes/log4j.xml

./target/classes/myorg/mypackage/ex1/App.class

./target/maven-status/...

./src/test/resources/log4j.xml

./src/test/java/myorg/mypackage/ex1/AppTest.java

./src/main/java/myorg/mypackage/ex1/App.java

• src/main/java classes were built in the target/classes directory by convention by the mavencompiler plugin that is automatically wired into JAR module builds. We didn't have to configure it because we structured our project using Maven directory structure and used the default packaging=jar module type (since packaging=jar is the default, it could be left unspecified).

Many of the standard features are enacted when for modules with packaging=jar type.

• src/test/java classes where built in the target/test-classes directory by convention by the maven-compiler plugin.

• src/test/resources where copied to the target/test-classes directory by convention by the maven-resources-plugin that is automatically wired into JAR module builds.

• test cases were run and their reports were placed in target/surefire-reports by convention by the maven-surefire-plugin that is automatically wired into JAR module builds.

23

Chapter 4. Creating Portable ...

• The build.xml and build.properties file from our work with Ant is still allowed to exist. We could even delegate from Maven to Ant using the maven-antrun-plugin if we had legacy build.xml

scripts that we wanted to leverage.

5. For *fun*, lets add a README that could be used to describe something about your project and have it be processed as part of the documentation for the module. You do not need to do this for class projects, but walking through this may be helpful in understanding how the class website is created from the source you have on your disk. Maven supports a couple of documentation generation languages, but lets just use HTML to keep this simple. Place the following content to src/site/resources/README.html

mkdir -p src/site/resources

$ cat src/site/resources/README.html

<?xml version="1.0"?>

< html >

< head >

< title > My First Project </ title >

</ head >

< body >

< section >< h1 > My First Project </ h1 ></ section >

< p />

This is my first project. Take a look at

< p />

< ul >

< li > this ....

</ li >

< li > that ....

</ li >

< li > or < a href = "./index.html" > go home </ a ></ li >

</ ul >

</ section >

</ body >

</ html >

6. The above is enough to provide the page. Now add a link to it from the project menu. Add the following content to src/site/site.xml

$ cat src/site/site.xml

<?xml version="1.0" encoding="UTF-8"?>

< project name = "${project.name}" >

< body >

< menu name = "Content" >

< item name = "README" href = "README.html" />

</ menu >

</ body >

</ project >

You must also specify a version# for the maven-project-info-reports-plugin. Maven is extremely version-aware.

< plugin >

< groupId > org.apache.maven.plugins

</ groupId >

24

< artifactId > maven-project-info-reports-plugin </ artifactId >

< version > 2.7

</ version >

</ plugin >

> find . -type f

./src/main/java/myorg/mypackage/ex1/App.java

./src/test/java/myorg/mypackage/ex1/AppTest.java

./src/test/resources/log4j.xml

./src/site/resources/README.html

./src/site/site.xml

./build.properties

./build.xml

./pom.xml

7. Build the site and open target/site/index.html in your browser. You should see a link to the

README on the left side.

$ mvn site

[INFO] Scanning for projects...

...

[INFO] BUILD SUCCESS

$ find target/site/ -name *.html

target/site/plugin-management.html

target/site/index.html

target/site/mail-lists.html

target/site/issue-tracking.html

target/site/license.html

target/site/project-info.html

target/site/dependency-info.html

target/site/README.html

target/site/dependencies.html

target/site/team-list.html

target/site/source-repository.html

target/site/integration.html

target/site/distribution-management.html

target/site/project-summary.html

target/site/plugins.html

Note

If you use the posted firstSimpleModuleEx as a starting point for your work you will need to re-enable site generation under the maven-site-plugin. This was turned off since the posted examples do not contain enough information to be posted with the rest of the class examples.

<!-- exclude this modules from site generation -->

< plugin >

< groupId > org.apache.maven.plugins

</ groupId >

< artifactId > maven-site-plugin </ artifactId >

25

Chapter 4. Creating Portable ...

< version > 3.4

</ version >

< configuration >

< skip > true </ skip >

< skipDeploy > true </ skipDeploy >

</ configuration >

</ plugin >

8. Okay, that was a lot of work to just copy an html file. Now lets add javadoc to our project and create a link to it. Add the following contents to the bottom of the pom.xml file.

< reporting >

< plugins >

< plugin >

< artifactId > maven-javadoc-plugin </ artifactId >

< groupId > org.apache.maven.plugins

</ groupId >

< version > 2.9.1

</ version >

< configuration >

< detectLinks />

< show > private </ show >

< source > 1.7

</ source >

< links >

< link > http://download.oracle.com/javaee/7/api/ </ link >

< link > http://download.oracle.com/javase/7/docs/api/ </ link >

</ links >

</ configuration >

</ plugin >

</ plugins >

</ reporting >

9. We could create a link the the apidocs/index.html like we did with README.html, but that would be something we'd keep having to update each time we added a new report. Lets add a property to the site.xml menu so a link to Javadoc and other reports can drop in automatically.

<?xml version="1.0" encoding="UTF-8"?>

< project name = "${project.name}" >

< body >

< menu name = "Content" >

< item name = "README" href = "README.html" />

</ menu >

< menu ref = "reports" />

</ body >

</ project >

10.Re-generate the site documentation with the site target. Open the target/site/index.html page and you should now see a menu item for "Project Reports" -> "JavaDocs". Our App class should be included in the Javadoc.

26

11.

Note

The pom.xml file is the main configuration source for 99% of what you develop with Maven. There is an additional $HOME/.m2/settings.xml file where you can specify build site-specific properties. These will be available to all pom.xml files. You want to be careful not to over-populate the settings.xml

file (taking advantage of its re-usable specification) since it will make you pom.xml files too dependent on a particulate build site. Refer to the Settings

Descriptor [http://maven.apache.org/maven-settings/settings.html] for detailed information on settings.xml. The following provides a step-wise generation of the settings.xml file you put in place during Development Environment Setup.

Read thru this for reference since you likely already have everything in place you need.

Let's start a settings.xml file to store properties that are specific to our build site. You can find details about each setting at the following URL [http://maven.apache.org/settings.html].

cat $HOME/.m2/settings.xml

<?xml version="1.0"?>

< settings 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/xsd/settings-1.0.0.xsd" >

</ settings >

12.If your $HOME directory path contains spaces, you will want to provide an override for the localRepository. Provide a custom path that does not contain spaces. This value will default to a "$HOME/.m2/repository" directory.

<!-- this overrides the default $HOME/.m2/repository location. -->

< localRepository > c:/jhu/repository </ localRepository >

13.Add the following specification to either the settings.xml file or the local pom.xml file. If you specify it to the local pom.xml file -- it will only apply to that project. If you specify it in the settings.xml file -- it will be global to all projects in your area. More will be covered on this later.

However, it should be noted that this profile is not active unless someone specifically asks for it (-Pdebugger) or the "debugger" environment variable is set (-Ddebugger=(anything)).

< profile >

< id > debugger </ id >

<!-- this should only be activated when performing interactive

debugging -->

< activation >

< property >

< name > debugger </ name >

</ property >

</ activation >

< properties >

27

Chapter 4. Creating Portable ...

< surefire.argLine

> -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -Xnoagent -

Djava.compiler=NONE </ surefire.argLine

>

</ properties >

</ profile >

14.Although not needed for this class -- at times you will need access to a dependency that is not available in a Maven repository. COTS libraries are generally not available at ibiblio.org. You must download it and manually install it locally.

This step will go though importing a stand-alone archive into the repository to resolve any dependencies. Start by declaring a dependency before we do the import. Note that a new scope property was added. See the Dependency Mechanism Intro Page [http://maven.apache.org/ guides/introduction/introduction-to-dependency-mechanism.html] for a discussion of scope, but in this case it is indicating that it should only be present on the command line and not the runtime classpath.

< dependency >

< groupId > foo </ groupId >

< artifactId > bar </ artifactId >

< version > 1.1

</ version >

< scope > provided </ scope >

</ dependency >

15.Attempt the build the module with the missing dependency. The build should fail but note that

Maven attempted all known external repositores.

> mvn package

[INFO] Scanning for projects...

[INFO]

[INFO] ------------------------------------------------------------------------

[INFO] Building My First Simple Project 1.0-SNAPSHOT

[INFO] ------------------------------------------------------------------------

Downloading: http://webdev.apl.jhu.edu/~jcs/maven2/foo/bar/1.1/bar-1.1.pom

Downloading: http://webdev.apl.jhu.edu/~jcs/maven2-snapshot/foo/bar/1.1/bar-1.1.pom

Downloading: http://repo1.maven.org/maven2/foo/bar/1.1/bar-1.1.pom

[WARNING] The POM for foo:bar:jar:1.1 is missing, no dependency information available

Downloading: http://webdev.apl.jhu.edu/~jcs/maven2/foo/bar/1.1/bar-1.1.jar

Downloading: http://webdev.apl.jhu.edu/~jcs/maven2-snapshot/foo/bar/1.1/bar-1.1.jar

Downloading: http://repo1.maven.org/maven2/foo/bar/1.1/bar-1.1.jar

[INFO] ------------------------------------------------------------------------

[INFO] BUILD FAILURE

[INFO] ------------------------------------------------------------------------

[INFO] Total time: 1.437s

[INFO] Finished at: Wed Feb 02 12:20:51 EST 2011

[INFO] Final Memory: 2M/15M

[INFO] ------------------------------------------------------------------------

[ERROR] Failed to execute goal on project ex1: Could not resolve dependencies for project

myorg.myproject:ex1:jar:1.0-SNAPSHOT:

Could not find artifact foo:bar:jar:1.1 in webdev-baseline (http://webdev.apl.jhu.edu/~jcs/maven2) -> [Help 1]

16.The old error message provided for Maven 2 was much better if a manual install is what you really needed. The newer (Maven 3) one does not provide instruction. In this case, manually

28

install a jar file that represents the declaration. Assign it a groupId of foo, an artifactId of bar, and a version of 1.1. Don't forget to add the -DgeneratePom=true or you will get a download warning everytime you try to build. All we need is a valid .jar file. If you don't have one laying around, just create one with valid structure.

$ touch bar.jar

$ mvn install:install-file -DgroupId=foo -DartifactId=bar -Dversion=1.1 -Dpackaging=jar -Dfile=bar.jar -

DgeneratePom=true

[INFO] Scanning for projects...

[INFO]

[INFO] ------------------------------------------------------------------------

[INFO] Building My First Simple Project 1.0-SNAPSHOT

[INFO] ------------------------------------------------------------------------

[INFO]

[INFO] --- maven-install-plugin:2.4:install-file (default-cli) @ ex1 ---

[INFO] Installing /home/jcstaff/proj/784/exercises/ex1/bar.jar to /home/jcstaff/.m2/repository2/foo/bar/1.1/bar-1.1.jar

[INFO] Installing /tmp/mvninstall5322334237902777597.pom to /home/jcstaff/.m2/repository2/foo/bar/1.1/bar-1.1.pom

[INFO] ------------------------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO] ------------------------------------------------------------------------

[INFO] Total time: 0.975 s

[INFO] Finished at: 2014-08-27T01:22:06-04:00

[INFO] Final Memory: 6M/105M

[INFO] ------------------------------------------------------------------------

17.After successfully installing the dummy archive, you should be able to locate the JAR and other supporting files in the local repository. Be sure to look where you have directed lcoalRepository in $HOME/.m2/settings.xml

$ find /home/jcstaff/.m2/repository2/foo/bar/

/home/jcstaff/.m2/repository2/foo/bar/

/home/jcstaff/.m2/repository2/foo/bar/1.1

/home/jcstaff/.m2/repository2/foo/bar/1.1/bar-1.1.pom.lastUpdated

/home/jcstaff/.m2/repository2/foo/bar/1.1/_remote.repositories

/home/jcstaff/.m2/repository2/foo/bar/1.1/bar-1.1.jar.lastUpdated

/home/jcstaff/.m2/repository2/foo/bar/1.1/bar-1.1.jar

/home/jcstaff/.m2/repository2/foo/bar/1.1/bar-1.1.pom

/home/jcstaff/.m2/repository2/foo/bar/maven-metadata-local.xml

18.

$ more /home/jcstaff/.m2/repository2/foo/bar/1.1/bar-1.1.pom

<?xml version="1.0" encoding="UTF-8"?>

< project xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 maven-4.0.0.xsd" xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" >

< modelVersion > 4.0.0

</ modelVersion >

< groupId > foo </ groupId >

< artifactId > bar </ artifactId >

< version > 1.1

</ version >

< description > POM was created from install:install-file </ description > http://maven.apache.org/xsd/

29

Chapter 4. Creating Portable ...

</ project >

19.Now try running "mvn package" and it should successfully resolve the fake dependency on the bar.jar.

20.One last thing...Maven pulls in defintions from many places in the build environment. If you ever want to know what the total sum of those sources are (the "effectice POM"), the execute the help:effective-pom goal.

$ mvn help:effective-pom

[INFO] Scanning for projects...

...

< project xmlns...

< modelVersion > 4.0.0

</ modelVersion >

< groupId > myorg.myproject

</ groupId >

< artifactId > ex1 </ artifactId >

< version > 1.0-SNAPSHOT </ version >

< name > My First Simple Project </ name >

< properties >

< jboss.home

> /opt/jboss-eap-6.1

</ jboss.home

>

< project.build.sourceEncoding

> UTF-8 </ project.build.sourceEncoding

>

</ properties >

< dependencies >

< dependency >

< groupId > foo </ groupId >

< artifactId > bar </ artifactId >

< version > 1.1

</ version >

< scope > provided </ scope >

</ dependency >

< dependency >

< groupId > commons-logging </ groupId >

< artifactId > commons-logging </ artifactId >

< version > 1.1.1

</ version >

...

4.1. Summary

During this exercise, you were able to establish a project which was understood by Maven. Once

Maven-compliant, each plugin can be added to perform different tasks for development. By the time we start adding databases, building EARs, and deploying to application servers, we can use all the plugin help we can get.

30

Chapter 5.

Leverage IDE using Eclipse

In this chapter we will be importing the project into the Eclipse IDE, running a few project goals, and demonstrating a debug session. IDEs provide very useful code navigation and refactoring tools to name only a few features. However, one of the unique tools offered by the IDEs is the ability to step through the code in a debugging session. Please do not end this exercise before becoming comfortable with the ability to use the debugger.

Note

Maven/Eclipse integration is probably the most volatile aspects of the environment.

Over the years the integration has progressed from a Maven plugin (maveneclipse-plugin; integrating from the Maven side on-demand) to Eclipse plugins

(m2e; continuously integrating from the Eclipse side dynamically). In the middle were phases where the Eclipse m2e plugin had to be manually installed after download. However, in recent versions, m2e has been pre-installed into Eclipse.

It gets easier to get started every day but harder to keep instructions up to date.

Warning

The Maven/Eclipse integration is a Maven-first approach where the Eclipse project always follows the Maven pom.xml. That is on of the main reasons this exercise started you with a pom.xml file first and progressed later to the IDE. It is wrong (or at least non-portable) to manually adjust the build path of a project within Eclipse. You must adjust the build path of a project by editing the pom.xml and having Eclipse automatically detect and follow that change.

5.1. Import a project into Eclipse

ex1 ~= firstSimpleModuleEx

The exercize asked you to name your module "ex1". This portion of the exercise demonstrates actions within Eclipse in a posted solution in your examples tree call

"firstSimpleModuleEx". You may/should continue to use your "ex1" solution from the previous chapters but know that firstSimpleModuleEx (as posted) has a few minor tweeks to its pom.xml to allow it to be distributed with the rest of the class examples as part of the class site.

1. Select File->Import->Maven->Existing Maven Projects, navigate to the directory with the project you have been working with and select OK.

31

Chapter 5. Leverage IDE using...

Figure 5.1. Import Existing Maven Project

2. The project should successfully import. Note that Eclipse has imported the project configuration from the Maven POM and has done at least the following...

Figure 5.2. Imported Project

32

Import a project into Eclipse

• Created three build packages; src/main/java, src/test/java, and src/test/resources. Had there been a src/main/resources we would have had a fourth build package added.

• Defined the project for use with Java 7.

• Added four Maven dependencies. Notice how the path to these dependencies are from the localRepository.

• Three of these dependencies (commons-logging, junit, and log4j) were through direct references. The fourth (hamcrest-core) is through a transitive dependency from junit. You can visualize this transitive dependency by opening the pom.xml and selecting the dependency hierarchy tab at the bottom of the window.

Figure 5.3. Visualizing Dependency Hierarchies

• You can get a text version of the dependency heirarchy using the Maven dependency:tree goal.

$ mvn dependency:tree

...

[INFO] ------------------------------------------------------------------------

[INFO] Building JavaSE::Simple Module Exercise Solution 1.0-SNAPSHOT

[INFO] ------------------------------------------------------------------------

[INFO]

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ firstSimpleModuleEx ---

[INFO] myorg.myproject:firstSimpleModuleEx:jar:1.0-SNAPSHOT

[INFO] +- commons-logging:commons-logging:jar:1.1.1:compile

[INFO] +- junit:junit:jar:4.10:test

[INFO] | \- org.hamcrest:hamcrest-core:jar:1.1:test

[INFO] \- log4j:log4j:jar:1.2.13:test

33

Chapter 5. Leverage IDE using...

• Make a small edit to the pom.xml and notice the change in the Maven Dependencies within

Eclipse. Switch back and forth between these two settings and watch the value under Maven

Dependencies follow the edits (be sure to save the file between edits).

< dependency >

< groupId > log4j </ groupId >

< artifactId > log4j </ artifactId >

< version > 1.2.13

</ version >

< scope > test </ scope >

</ dependency >

< dependency >

< groupId > log4j </ groupId >

< artifactId > log4j </ artifactId >

< version > 1.2.12

</ version >

< scope > test </ scope >

</ dependency >

Figure 5.4. Maven/Eclipse Dependency Coordination

5.2. Setup Eclipse to be able to execute Maven project goals

1. Right-click on the pom.xml file or project folder and execute Run As->Maven install". You can also get back to this window through the Run As option on the toolbar once you have the project selective. This mode runs the JUnit test you wrote within the context of the full maven project.

All pre-test and post-test setup and teardown you wired into the Maven command line build will be executed.

34

Figure 5.5. Run As Targets

Setup Eclipse to be able to execute Maven project goals

Note that you can create a separate window for any of the Eclipse tabs. Using dual monitors --

I commonly display the IDE on one page the the Console output on another when using debug statements.

35

Chapter 5. Leverage IDE using...

Figure 5.6. Run As: Maven Install Output

2. Rerun the tests as a JUnit test. This mode runs the JUnit test raw within Eclipse. This is very efficient for making and testing Java code changes but will not run any maven setup or teardown plugins (which is not always required or can be avoided).

36

Setup environment to enable interactive debugging

Figure 5.7. Run As: JUnit Test Results

Always Make Projects Eclipse/JUnit-Friendly

Maven is a very useful and powerful tool. However, there is a point where the information from Maven has been captured by the IDE and we don't need to run full Maven builds (e.g., RunAs: Maven Install). As you saw from the RunAs:

JUnit test we were able to run the unit test and run it exceptionally fast without

Maven. I strongly recommend making your unit tests Eclipse/JUnit-friendly so that you can work efficiently in certain areas. That means hard-code reasable defaults without relying on the maven-surefire-plugin passing in properties from the outside environment. Allow overrides, but code in a usable default into the test.

5.3. Setup environment to enable interactive debugging

There are two primary ways to use the debugger; separate/remote process and embedded (within

Eclipse). The later is much easier to use but is limited by what you can execute within the Eclipse

IDE. The second takes a minor amount of setup but can be re-used to debug code running within application servers on your local and remote machines.

1. Lets start with a remote debugging session by recalling the profile you were asked to add to either your pom.xml or settings.xml. If you have not done so, you can add it to either at this time.

<profiles>

<profile> <!-- tells surefire to run JUnit tests with remote debug -->

37

Chapter 5. Leverage IDE using...

<id>debugger</id>

<activation>

<property>

<name>debugger</name>

</property>

</activation>

<properties>

<surefire.argLine>-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -Xnoagent -

Djava.compiler=NONE</surefire.argLine>

</properties>

</profile>

</profiles>

2. Add a definition for the "surefire.argLine" within the maven-surefire-plugin declaration. Surefire is already being pulled into the project, this declaration just specifies the extra configuration along with a specific version. Maven will start complaining ig you leave off that version.

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-surefire--plugin</artifactId>

<version>2.17</version>

<configuration>

<argLine>${surefire.argLine}</argLine>

</configuration>

</plugin>

3. Uncomment (or re-add) your failure test in AppTest.java.

@Test public void testFail() {

//System.out.println("testFail");

log.info( "testFail" );

App app = new App();

assertTrue( "app didn't return 0" , app.returnOne() == 0 );

}

4. Execute a Run As Maven test. My selecting the project, right clicking and chosing the right target. You should see the following error in the console.

Running myorg.mypackage.ex1.AppTest

INFO 28-08 23:52:31,809 (AppTest.java:testApp:17) -testApp

DEBUG 28-08 23:52:31,821 (App.java:returnOne:11) -Here's One!

INFO 28-08 23:52:31,829 (AppTest.java:testFail:25) -testFail

DEBUG 28-08 23:52:31,831 (App.java:returnOne:11) -Here's One!

Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.409 sec <<< FAILURE!

testFail(myorg.mypackage.ex1.AppTest) Time elapsed: 0.016 sec <<< FAILURE!

java.lang.AssertionError: app didn't return 0

at org.junit.Assert.fail(Assert.java:93)

at org.junit.Assert.assertTrue(Assert.java:43)

at myorg.mypackage.ex1.AppTest.testFail(AppTest.java:27)

38

Setup environment to enable interactive debugging

5. Click on the hyperlink to one of the lines in the project source code in the failure stack trace.

Place a breakpoint at that line by double-clicking on the line number. A blue ball should appear to the left of the numbers.

6. Debug As->Maven build..., change the base directory to a re-usable ${project_loc} variable, assign the "test" goal, and activate the "debugger" profile. Click "Debug" when finished. It will automatically save.

Figure 5.8. Setting up Maven/Eclipse Remote Debugging

You should see the Maben build start but pause before executing the first JUnit test. Think of this as the "server-side" of the debugger session.

[INFO] --- maven-surefire-plugin:2.17:test (default-test) @ firstSimpleModuleEx ---

[INFO] Surefire report directory: /home/jcstaff/workspaces/ejava-class/ejava-student/javase/firstSimpleModuleEx/ target/surefire-reports

-------------------------------------------------------

T E S T S

-------------------------------------------------------

Listening for transport dt_socket at address: 8000

7. Start the "client-side" of the debugger session by clicking on the bug pulldown at the top of the window. Select debug configurations, double click on Remote Java Application, select your project, and notice the localhost:8000 that came up agrees with your server-side configuration.

Press "Debug" when you are ready and answer the prompt to change you view.

39

Chapter 5. Leverage IDE using...

Figure 5.9. Setting up Maven/Eclipse Client Side

8. The IDE should switch to a debugger view and be waiting at the line you set the breakpoint on. From here you can look at the state of any variables (we don't have any) and step into the next call.

40

Figure 5.10. Debugger Breakpoint

Summary

9. Once you are done with the debugging session you can click continue (agreen right arrow at top) or detach from the server (red swiggly line at top).

10.Terminate the debugger session, retun to one of the JavaEE or Java-based views. Select a specific JUnit test, test method, package, or entire application and click Debug As JUnit test.

11.Note the IDE again switches to the Debug view and is stopped at the breakpoint. You may step into the call, look at the state of any variable, or terminate the program (red square at top of window).

5.4. Summary

In this chapter, you were able to integrate your Maven and Eclipse environments. This allows you to leverage the Maven plugins as your core build system and leverage Eclipse for developing the content.

As mentioned, Eclipse will be the primary demonstration environment in class, but you may use other IDEs, like NetBeans, to match personal preferences. It is my belief that anything Eclipse can do -- the other leading IDEs can do as well. There have been many occasions where students

41

Chapter 5. Leverage IDE using...

very familiar with an alternate IDE have picked up the Maven aspects and handled the integration with their IDE without issue.

You have now completed this exercise and as a part of it you were able to create a project tree, build/test the project manually, build/test the project using Ant build tool, build/test the project using the Maven build system, and integrate the project with an IDE for faster and more detailed development. We have covered a lot but clearly we have only touched a small amount of what can be done. Luckily one doesn't have to know how to do it all right away in order to be productive.

42

Download