Upload
others
View
7
Download
0
Embed Size (px)
Citation preview
JavaSE: First Simple Module Exercise
A Basic Introduction to
JavaSE and Maven Modules
Revision: v2018-08-14Built on: 2019-08-22 07:08 EST
Copyright © 2019 jim stafford ([email protected])
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.
iii
Purpose ............................................................................................................................ v
1. Goals .................................................................................................................... v
2. Objectives ............................................................................................................. v
1. Develop and Test Module using Command Line Tools (OPTIONAL!) ........................... 1
1.1. Summary ............................................................................................................ 5
2. Automate Build and Testing with Ant (OPTIONAL!) .................................................... 7
2.1. Summary .......................................................................................................... 14
3. Adding Logging ......................................................................................................... 15
3.1. Summary .......................................................................................................... 19
4. Creating Portable and Repeatable Project Builds with Maven ................................... 21
4.1. Summary .......................................................................................................... 33
5. Leverage IDE using Eclipse ....................................................................................... 35
5.1. Import a project into Eclipse .............................................................................. 35
5.2. Setup Eclipse to be able to execute Maven project goals ..................................... 39
5.3. Setup environment to enable interactive debugging ............................................. 42
5.4. Summary .......................................................................................................... 47
iv
v
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.
vi
Chapter 1.
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 shell-
specific. 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 shell 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. M2_REPO is the path to your Maven repository.
export CLASS_HOME=$HOME/proj/784
export PROJECT_BASEDIR=$CLASS_HOME/exercises/ex1
mkdir -p $PROJECT_BASEDIR
cd $PROJECT_BASEDIR
export M2_REPO=$HOME/.m2/repository
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
| `-- test
Chapter 1. Develop and Test M...
2
| |-- resources
| `-- java
| `-- myorg
| `-- mypackage
| `-- ex1
`-- target
|-- classes
|-- test-classes
`-- test-reports
mkdir -p src/main/java/myorg/mypackage/ex1
mkdir -p src/test/java/myorg/mypackage/ex1
mkdir -p src/test/resources
mkdir -p target/classes
mkdir -p src/test/java/myorg/mypackage/ex1
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);
3
}
}
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.
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_JARS="$M2_REPO/junit/junit/4.12/junit-4.12.jar:$M2_REPO/org/hamcrest/hamcrest-core/1.3/
hamcrest-core-1.3.jar"
javac -classpath "target/ex1.jar:$JUNIT_JARS" 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 directory,
your unit test class from src/test compiled into target/test-classes directory, and the Java archive
with the production class is in target directory.
target
|-- classes
| `-- myorg
| `-- mypackage
| `-- ex1
| `-- App.class
|-- ex1.jar
|-- test-classes
| `-- myorg
| `-- mypackage
| `-- ex1
Chapter 1. Develop and Test M...
4
| `-- AppTest.class
`-- test-reports
8. Run the JUnit test framework.
java -classpath "target/ex1.jar:$JUNIT_JARS:target/test-classes" org.junit.runner.JUnitCore
myorg.mypackage.ex1.AppTest
JUnit version 4.12
.testApp
Here's One!
Time: 0.003
OK (1 test)
9. Change add/remove a test that will fail, re-compile the test class, and re-run.
//AppTest.java
@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_JARS" src/test/java/myorg/mypackage/ex1/AppTest.java -d target/test-
classes
java -classpath "target/ex1.jar:$JUNIT_JARS:target/test-classes" org.junit.runner.JUnitCore
myorg.mypackage.ex1.AppTest
JUnit version 4.12
.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
Summary
5
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.
6
Chapter 2.
7
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 non-
portable 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
Chapter 2. Automate Build and...
8
• 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 [https://
ant.apache.org/manual/Tasks/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/jim/proj/784/exercises/ex1/build.xml
echo:
[echo] basedir=/home/jim/proj/784/exercises/ex1
[echo] artifactId=ex1
[echo] src.dir=/home/jim/proj/784/exercises/ex1/src
[echo] build.dir=/home/jim/proj/784/exercises/ex1/target
[echo] junit.classpath=/home/jim/.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 Mkdir docs [https://ant.apache.org/manual/Tasks/
mkdir.html] for more infomation.
• javac - compiles java sources files. See Ant Javac docs [https://ant.apache.org/manual/
Tasks/javac.html] for more information. Note that we are making sure we get JavaSE 8
classes compiled.
9
• jar - builds a java archive. See Ant Jar Docs [https://ant.apache.org/manual/Tasks/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.8"
target="1.8"
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/jim/proj/784/exercises/ex1/build.xml
package:
[mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes
[javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes
[jar] Building jar: /home/jim/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
Chapter 2. Automate Build and...
10
./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.8"
target="1.8"
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/jim/proj/784/exercises/ex1/build.xml
package:
[mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes
[javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes
[jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar
test:
[mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-classes
[javac] Compiling 1 source file to /home/jim/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
11
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 Ant docs [https://
ant.apache.org/manual/Tasks/junit.html] 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
A few years ago when 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/jim/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/jim/.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.
Chapter 2. Automate Build and...
12
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/jim/proj/784/exercises/ex1/target/classes
[javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes
[jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar
test:
[mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-classes
[javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/test-classes
[mkdir] Created dir: /home/jim/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
13
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 [https://
ant.apache.org/manual/Tasks/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/jim/proj/784/exercises/ex1/build.xml
clean:
[delete] Deleting directory /home/jim/proj/784/exercises/ex1/target
package:
[mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes
[javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes
[jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar
test:
[mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-classes
[javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/test-classes
[mkdir] Created dir: /home/jim/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() {
$ ant clean test
Buildfile: /home/jim/proj/784/exercises/ex1/build.xml
clean:
[delete] Deleting directory /home/jim/proj/784/exercises/ex1/target
package:
[mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes
[javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes
[jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar
Chapter 2. Automate Build and...
14
test:
[mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-classes
[javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/test-classes
[mkdir] Created dir: /home/jim/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 actions 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 open-
ended, 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.
Chapter 3.
15
Adding LoggingIn 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 course uses the SLF API and and the log4j implementation.
1. Change the System.out() calls in App and AppTest from Part A to use SLF logging API. The
slf4j-api Javadoc [https://www.slf4j.org/apidocs/org/slf4j/package-summary.html] and manual
[https://www.slf4j.org/manual.html] will be helpful in understanding this interface.
package myorg.mypackage.ex1;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class App {
private static Logger logger = LoggerFactory.getLogger(App.class);
public int returnOne() {
//System.out.println( "Here's One!" );
logger.debug( "Here's One!" );
return 1;
}
public static void main( String[] args ) {
//System.out.println( "Hello World!" );
logger.info( "Hello World!" );
}
}
package myorg.mypackage.ex1;
...
import static org.junit.Assert.*;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AppTest {
private static Logger logger = LoggerFactory.getLogger(AppTest.class);
...
@Test
public void testApp() {
//System.out.println("testApp");
logger.info("testApp");
App app = new App();
assertTrue("app didn't return 1", app.returnOne() == 1);
}
}
Chapter 3. Adding Logging
16
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 manual [https://
logging.apache.org/log4j/1.2/manual.html] for possible information on how to configure and use
log4j. It doesn't matter whether you use a log4j.xml format or log4j.properties format. Of note,
the implementation we are using is based on "Log4j 1" -- which reached its end of life in 2015.
It still works but all energy in this area is within "Log4j 2".
<?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>
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
17
of specifying the correct configuration file at runtime. Our test classes and
resources are not a part of follow-on deployment.
3. Add the slf4j-api.jar to the compile classpaths and the slf4j-api.jar, slf4j-log4j.jar, and log4j.jar
to the runtime classpath used during tests. 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
junit.classpath=${M2_REPO}/junit/junit/4.12/junit-4.12.jar:\
${M2_REPO}/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar
slf4j-api.classpath=${M2_REPO}/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar
slf4j-log4j.classpath=${M2_REPO}/org/slf4j/slf4j-log4j12/1.7.25/slf4j-log4j12-1.7.25.jar
log4j.classpath=${M2_REPO}/log4j/log4j/1.2.17/log4j-1.2.17.jar
<target name="echo">
...
<echo>slf4j-api.classpath=${slf4j-api.classpath}</echo>
<echo>slf4j-log4j.classpath=${slf4j-log4j.classpath}</echo>
<echo>log4j.classpath=${log4j.classpath}</echo>
</target>
<javac srcdir="${src.dir}/main/java"
destdir="${build.dir}/classes"
debug="true"
source="1.8"
target="1.8"
includeantruntime="false">
<classpath>
<pathelement path="${slf4j-api.classpath}"/>
</classpath>
</javac>
<javac srcdir="${src.dir}/test/java"
destdir="${build.dir}/test-classes"
debug="true"
source="1.8"
target="1.8"
includeantruntime="false">
<classpath>
<pathelement location="${build.dir}/${artifactId}.jar"/>
<pathelement path="${junit.classpath}"/>
<pathelement path="${slf4j-api.classpath}"/>
</classpath>
</javac>
<copy todir="${build.dir}/test-classes">
<fileset dir="${src.dir}/test/resources"/>
</copy>
<junit printsummary="true" fork="true">
Chapter 3. Adding Logging
18
<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 13-08 18:24:21,983 [myorg.mypackage.ex1.AppTest] (AppTest.java:testApp:18) -testApp
DEBUG 13-08 18:24:21,986 [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
./src/test/java/myorg/mypackage/ex1/AppTest.java
./src/test/resources/log4j.xml
Summary
19
./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 slf4j-api to the javac compiler task because log4j was never directly referenced in
the source code. We added a runtime dependency on log4j (and the slf4j-log4j adapter) 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.
20
Chapter 4.
21
Creating Portable and Repeatable
Project Builds with MavenIn 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. If
you want to make sure that all dependencies have been resolved, use the mvn
dependency:go-offline goal to eagerly resolve all dependencies.
Chapter 4. Creating Portable ...
22
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 only necessary to if we need to override the
default Java version -- like what we did in our Ant script.
• 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 when inside the IDE. 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
23
using artifacts 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>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
Chapter 4. Creating Portable ...
24
</plugin>
</plugins>
</build>
</project>
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-the-
lifecycle.html#Lifecycle_Reference] I refer to this page very often.
$ mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building JavaSE::Simple Module Exercise Solution 5.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ firstSimpleModuleEx ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/jim/proj/784/exercises/ex1/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ firstSimpleModuleEx ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ firstSimpleModuleEx ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.7.0:testCompile (default-testCompile) @ firstSimpleModuleEx ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/test-classes
[INFO]
25
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ firstSimpleModuleEx ---
[INFO] Surefire report directory: /home/jim/proj/784/exercises/ex1/target/surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running myorg.mypackage.ex1.AppTest
INFO 13-08 19:03:19,264 (AppTest.java:testApp:18) -testApp
DEBUG 13-08 19:03:19,267 (App.java:returnOne:11) -Here's One!
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.13 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ firstSimpleModuleEx ---
[INFO] Building jar: /home/jim/proj/784/exercises/ex1/target/firstSimpleModuleEx-5.0.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.553 s
[INFO] Finished at: 2018-08-13T19:03:19-04:00
[INFO] Final Memory: 19M/309M
[INFO] ------------------------------------------------------------------------
[WARNING] The requested profile "h2db" could not be activated because it does not exist.
Ignore WARNING for Non-existent Profile
You were asked to declare a h2db profile as active within $HOME/.m2/
settings.xml during the software installation instructions. Maven will warn you
about any profile you request but is not found within the module to help identify
spelling errors. In this case, we simply do not need the profile and have not
defined it.
4. The contents of your development tree should look as follows.
> 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
Chapter 4. Creating Portable ...
26
• src/main/java classes were built in the target/classes directory by convention by the maven-
compiler 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.
• 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
course 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
27
<?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-site-plugin and maven-project-info-reports-
plugin in the pom.xml. Maven is extremely version-aware.
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.4</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>2.8</version>
</plugin>
</plugins>
> 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
Chapter 4. Creating Portable ...
28
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>
<version>3.4</version>
<configuration>
<skip>false</skip>
<skipDeploy>false</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.
...
</build>
<reporting>
<plugins>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>3.0.1</version>
<configuration>
<detectLinks>false</detectLinks>
<detectOfflineLinks>true</detectOfflineLinks>
<show>private</show>
<source>1.8</source>
<additionalparam>-Xdoclint:none</additionalparam>
<failOnError>false</failOnError>
<links>
<link>http://download.oracle.com/javase/8/docs/api/</link>
<link>https://javaee.github.io/javaee-spec/javadocs/</link>
</links>
</configuration>
</plugin>
29
</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.
# 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>
<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.
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 particular build location. 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>
Chapter 4. Creating Portable ...
30
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)).
<profiles>
<profile>
<id>debugger</id>
<!-- this should only be activated when performing interactive
debugging -->
<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>
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.
31
> 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
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/jim/proj/784/exercises/ex1/bar.jar to /home/jim/.m2/repository/foo/bar/1.1/bar-1.1.jar
[INFO] Installing /tmp/mvninstall5322334237902777597.pom to /home/jim/.m2/repository/foo/bar/1.1/bar-1.1.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[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 localRepository
in $HOME/.m2/settings.xml
Chapter 4. Creating Portable ...
32
$ find /home/jim/.m2/repository/foo/bar/
/home/jim/.m2/repository/foo/bar/
/home/jim/.m2/repository/foo/bar/1.1
/home/jim/.m2/repository/foo/bar/1.1/bar-1.1.pom.lastUpdated
/home/jim/.m2/repository/foo/bar/1.1/_remote.repositories
/home/jim/.m2/repository/foo/bar/1.1/bar-1.1.jar.lastUpdated
/home/jim/.m2/repository/foo/bar/1.1/bar-1.1.jar
/home/jim/.m2/repository/foo/bar/1.1/bar-1.1.pom
/home/jim/.m2/repository/foo/bar/maven-metadata-local.xml
18.Notice that Maven always makes sure there is a POM file present -- whether it had to generate
it or not.
$ more /home/jim/.m2/repository/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 http://maven.apache.org/xsd/
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>
</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 definitions from many places in the build environment. If you
ever want to know what the total sum of those sources are (the "effective 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/wildfly-13.0.0.Final</jboss.home>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>foo</groupId>
<artifactId>bar</artifactId>
<version>1.1</version>
Summary
33
<scope>provided</scope>
</dependency>
...
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.
34
Chapter 5.
35
Leverage IDE using EclipseIn 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 was once the most volatile aspects of the environment.
However, over the years the two have been designed to work well together as long
as you keep things Maven-centric.
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 exercise 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.
Chapter 5. Leverage IDE using...
36
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...
Import a project into Eclipse
37
Figure 5.2. Imported Project
• 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 8.
• Added five Maven dependencies. Notice how the path to these dependencies are from the
localRepository.
• Four of these dependencies (slf-api, junit, slf-log4j, and log4j) were through direct references.
The fifth (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.
Chapter 5. Leverage IDE using...
38
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:5.0.0-SNAPSHOT
[INFO] +- org.slf4j:slf4j-api:jar:1.7.25:provided
[INFO] +- junit:junit:jar:4.12:test
[INFO] | \- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] +- org.slf4j:slf4j-log4j12:jar:1.7.25:test
[INFO] \- log4j:log4j:jar:1.2.17:test
• 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>
Setup Eclipse to be able to execute Maven project goals
39
<version>1.2.17</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</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.
Chapter 5. Leverage IDE using...
40
Figure 5.5. Run As Targets
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.
Setup Eclipse to be able to execute Maven project goals
41
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).
Chapter 5. Leverage IDE using...
42
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 second is much easier to use but is limited by what you can execute within the
Eclipse IDE. The first 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.
Setup environment to enable interactive debugging
43
<profiles>
<profile> <!-- tells surefire to run JUnit tests with remote debug -->
<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)
Chapter 5. Leverage IDE using...
44
at myorg.mypackage.ex1.AppTest.testFail(AppTest.java:27)
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 Maven 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
Setup environment to enable interactive debugging
45
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.
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.
Chapter 5. Leverage IDE using...
46
Figure 5.10. Debugger Breakpoint
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).
Debug-As Junit Test Is Even Easier
I walked you through the harder debugging session setup that is only necessary
when the problem is occurring in code running in a remote JVM. If the code can
be executed using "Run-As -> JUnit Test", then simply set a breakpoint and use
"Debug-As -> JUnut Test"
Summary
47
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
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.
48