Saturday, September 17, 2011

Learning Apache Commons Digester 3

Introduction 

Apache Commons Digester 3 is a Java library to translate XML data to Java objects. It makes configuring Java applications with XML files much easier than other wise. In this tutorial, we are going to create a Family (Listing 2), an Address (Listing 3), and three Member (Listing 4) objects corresponding to the XML data in Listing 1.

To master Apache Common Disgester 3, one must really understand the key concepts: rules, matching patterns, and the object stacks.

Rules and Matching Patterns



A rule is a instance of a subclass of the Rule class, representing a set of actions (more on this later). For a rule to have effect, it must be registered on a matching pattern for XML elements (see examples in Listing 7 - Registering Rules), and must be associated with a digester (see an example at Line 24, Listing 6 - Parsing XML). When the digester walks through the XML element tree during the parsing phase, it will invoke the actions of a rule when it encounters the elements matched by the pattern. More specifically, the digester will call the begin(), body(), and end() methods on the rule object when it encounters the beginning tag, content, and ending tag of a matched element, respectively. The actions of a rule are implemented in the body of its begin(), body(), and end() methods. For the sake of briefness, we are going to just refer to the begin(), body(), end() methods as the begin(), body(), end() actions. And we are going to refer to an action invoked when the digester encounters a certain XML element as an action for the XML element; and the owner rule of the action as a rule for the XML element. Of course, a rule may be a rule for Element A and Element B at the same time as long as it is registered on patterns that match Element A and Element B.

The matching pattern syntax is very simply. For elements in Listing 1,
  • the <family> element can be matched by pattern "family"
  • the <address> element by either "family/address" or "*/address"
  • a <firstname> element by "family/member/firstname" or "*/firstname".

An action can be anything. The most frequent actions are creating Java objects, setting JavaBean properties with XML element contents or attribute values, and linking a Java object with another.  There are many built in subclasses of the Rule class, for example, ObjectCreateRule,  SetPropertiesRule, BeanPropertySetterRule,  SetNextRule, etc, and are just for those purposes.

Order of Actions

For rules registered on patterns that match different elements, the order of rule registration does not matter. A begin() action for a XML element is always invoked before any actions for its nested elements; similarly, a end() action for a XML element is always invoked after any actions for its nested elements. If there are two rules for the same element, say Rule A is registered on a pattern that matches the element before Rule B is regiestered on the same pattern or another pattern that also matches the element, the order of action execution will be:
  1. Rule A's begin() action
  2. Rule B's begin() action
  3. Rule B's end() action
  4. Rule A's end action

The Object Stacks

A digester maintains many object stacks. One is called the default stack, another is the parameter stack. In addition, it may hold any number of named stacks. Java objects created during the parsing process are pushed to and popped out of the stacks (by the rules). Many built-in rules, such as SetPropertiesRule, BeanPropertySetterRule, and CallMethodRule, just work on the object on the top of the default stack. An ObjectCreateRule creates a new Java object and pushes it to the default stack during its begin() method execution, and pops it out during its end() method execution.

Any Rule object can call its getDigester() method to retrieve a reference to the digester that it associates with. Via the digester, a rule can push object to, pop objects out, or peek objects in the default stack, the parameter stack, or any named stack by calling the digester's methods:
  • push() - push to the default stack
  • pop() - pop out the default stack
  • peek() - peek into the default stack
  • pushParams() - push to the parameter stack
  • popParams() - pop out the parameter stack
  • peekParams() - peek into the parameter stack
  • push(stackName) - push to the named stack
  • pop(stackName) - pop out the named stack
  • peek(stackName) - peek into the named stack
When you create your own rule class that pushes to/pops from a stack. You better use a named stack specific for that rule class to avoid intervention with the built-in rule classes, which use the default stack, or other rule classes created by you. Of course, a rule class should only pop object (in its end() method) that was pushed by itself (in its begin() method). Be aware of the fact that a stack of a disgeter is shared by all rules associate with it. If two rules for the same element push to/pop out the same stack, it will be difficult for other rules on that element, or elements nested in it to know which object is on the top of the stack at the time of its actions. The stacks, particularly the default stack, give the developers a lot of convenience. They, however, also introduce a lot of tight coupling among the rule actions. Treat them as sharp knifes that can easily cut fingers accidentally.

Calling the parse() method on a digester returns the object at the bottom of the default stack. For example, the call to the parse() method at Line 26, Listing 6 returns a Family object for this object is created when the digester encounters the <family> element and is the first object pushed to the default stack.


A Simple Example

Listing 1 - family.xml

<family name='Addison'>
    <address city='New York' state='New York' country='USA'>
        <street>Apt. 3522, 10 West Street</street>
    </address>
    
    <member>
        <firstname>Thomas</firstname>
        <gender>M</gender>
        <age>25</age>
    </member>
    
    <member>    
        <firstname>Linda</firstname>
        <gender>F</gender>
        <age>24</age>
    </member>
    
    <member>    
        <firstname>Alice</firstname>
        <gender>F</gender>
        <age>1</age>
    </member>
</family>

The following three classes in Listing 2, 3, and 4 are simple Java class with basically setter/getter methods. The only thing that readers should pay a little attention is that the Family class has an addMember() method to add a member to the family per call (Line 32 - 34, Listing 2).

Listing 2 - The Family class

package commons.digester3.example;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Family {
    private String name;
    private List<Member> members = new ArrayList<Member>();
    private Address address;
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }    

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public List<Member> getMembers() {
        return Collections.unmodifiableList(members);
    }
    
    public void addMember(Member member) {
        members.add(member);
    }
}

Listing 3 - The Address class

package commons.digester3.example;

public class Address {
    private String street;
    private String city;
    private String state;
    private String country;
    
    public String getCity() {
        return city;
    }
    
    public void setCity(String city) {
        this.city = city;
    }
    
    public String getState() {
        return state;
    }
    
    public void setState(String state) {
        this.state = state;
    }
    
    public String getCountry() {
        return country;
    }
    
    public void setCountry(String country) {
        this.country = country;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }
}

Listing 4 - The Member class

package commons.digester3.example;

public class Member {
    private String firstname;
    private char gender;
    private int age;
    
    public String getFirstname() {
        return firstname;
    }
    
    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }
    
    public char getGender() {
        return gender;
    }
    
    public void setGender(char gender) {
        this.gender = gender;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
}

Listed in Listing 5 is the code to parse the XML and to create the Family, Address, and Member objects. The only Digester-specific code are at Line 24 - 26. The FamilyModule class is a rule module class, which we are going to discuss later in this tutorial. From the code in Listing 5, we can see that a Family object is returned from the call to the parse() method on a Digester object. In fact, the Address and Member objects are also created and associated with the Family object. We can see it via the unit testing code in Listing 6.


Listing 5 - Parsing XML

package commons.digester3.example;

import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.binder.DigesterLoader;
import org.xml.sax.SAXException;

public class FamilyCreator {    
    /**
     * Creates a Family object (and Address, Member objects contained by it) based
     * on XML data.
     * 
     * @param source - name of the XML file
     * 
     * @throws SAXException
     * @throws IOException
     */
    public static Family createFamily(String source) throws SAXException, IOException {
        Family result = null;
        InputStream inputStream = FamilyModule.class.getClassLoader().getResourceAsStream(source);
        
        DigesterLoader digesterLoader = DigesterLoader.newLoader(new FamilyModule());
        Digester digester = digesterLoader.newDigester();    
        result = digester.parse(inputStream);
        
        return result;
    }    
}

Listing 6 - JUnit Tests

package commons.digester3.example;

import java.io.IOException;
import java.util.List;

import junit.framework.Assert;

import org.junit.BeforeClass;
import org.junit.Test;
import org.xml.sax.SAXException;

public class SimpleDigesterTest {
    private static Family family = null;
    
    @BeforeClass
    public static void setup() throws IOException, SAXException {
        family = FamilyCreator.createFamily("family.xml");
    }
    
    @Test
    public void testFamily() {
        Assert.assertNotNull("Family was not created.", family);
        
        Assert.assertEquals("Incorrect family last name", "Addison", family.getName());
    }
    
    @Test
    public void testAddress() {
        Address address = family.getAddress();
        
        Assert.assertNotNull("Address was not created.", address);
        
        Assert.assertEquals("Incorrect street line", "Apt. 3522, 10 West Street", address.getStreet());
        Assert.assertEquals("Incorrect city", "New York", address.getCity());
        Assert.assertEquals("Incorrect state", "New York", address.getState());
        Assert.assertEquals("Incorrect coutry", "USA", address.getCountry());
    }
    
    @Test
    public void testMember() {
        List>Member< members = family.getMembers();
        Assert.assertNotNull("Family members were not created.", members);
        
        Assert.assertEquals("Incorrect member count.", 3, members.size());
        
        Member member = members.get(1);
        Assert.assertEquals("Incorrect first name", "Linda",member.getFirstname());
        Assert.assertEquals("Incorrect gender", 'F', member.getGender());
        Assert.assertEquals("Incorrect age", 24, member.getAge());
    }
}

The FamilyModule class in Lising 7 is a rule module class. A rule module class is basically a set of pairs of rule and matching pattern. A digester will take a rule module (see Line 24 in Listing 5) to figure out which rule to fire for which element. The in-line comments explain the rules.

Listing 7 - Registering Rules

package commons.digester3.example;

import org.apache.commons.digester3.binder.AbstractRulesModule;

public class FamilyModule extends AbstractRulesModule {
    
    @Override
    protected void configure() {

        // Register a ObjectCreatRule on matching pattern "family". Later on, in the parsing phase, 
        // when encounters a <family> element, the digester will fire this rule to create a Family object.
        // Also register a SetPropertiesRule on the same pattern. Later on, in the parsing phase,
        // the digester will fire this rule to set properties of the Family object  
        // with the attribute values of the <family> element
        // For the setProperties() to work this way, a property name must be the same as the attribute name.
        forPattern("family").createObject().ofType("commons.digester3.example.Family")
                .then().setProperties();
        

        // ... Also register a SetNextRule on matching pattern "family/address" to establish relationship 
        // between the Family and the Address object by calling the setAddress() method on the Family
        // object (expected to be the object next to top of the default stack) and passing the Address object
        // (expected to be the object on top of the default stack) as argument to it.
        forPattern("family/address").createObject().ofType("commons.digester3.example.Address")
                .then().setProperties()
                .then().setNext("setAddress");
        
        // Register a BeanPropertySetterRule on matching pattern "family/address/street", to
        // set the property of the Address object named street with the content of the <street>
        // element.
        forPattern("family/address/street").setBeanProperty();
        

        // ... to establish relationship between the Family and the Member object by calling
        // the addMember() method on the Family object and passing the Member object as argument to it.
        forPattern("family/member").createObject().ofType("commons.digester3.example.Member")
                .then().setNext("addMember");
        
        forPattern("family/member/firstname").setBeanProperty();
        forPattern("family/member/gender").setBeanProperty();
        forPattern("family/member/age").setBeanProperty();
    }
}

Beyond The Simplest

Mismatch Between Attribute and Property Name

In out example above, all element attribute names match the corresponding JavaBean property names. What we have to do, if there is a mismatch, for example, the <family> element has an attribute named "name", but the Family object has setLastname() and getLastname() methods? All we have to do is to make an addAlias(attributeName, propertyName) call after calling setProperties(). For example, instead of have Line 15 - 16 in Listing 7, we are going to have the following code:

forPattern("family").createObject().ofType("commons.digester3.example.Family")
        .then().setProperties().addAlias("name", "lastname");

Mismatch Between Nested Element and Property Name

In our example above, all nested element names match the corresponding JavaBean property names. For example, a <member> element has nested elements <firstname>, <age>, and <gender>, and the corresponding Member object has setFirstname(), setGender(), and setAge(). What we have to do, if there is a mismatch, for example, the <member> element has nexted <name> instead of <firstname>? All we have to do is to change the matching pattern and make an withName(propertyName) call after calling setBeanProperty(). For example, instead of have Line 38 in Listing 7, we are going to have the following code:

forPattern("family/member/name")
        .setBeanProperty().withName("firstname");

Using CallMethodRule

Some mismatches are OK. Digester has some default converter to convert them. For example, even though all element attribute values and contents are strings, properties of type char, int, will not need explicit conversion. In our example, age of a member is of type int, and Digester implicitly converts a string into an int.

Some type mismatches will be issues. Suppose that our Member class is like in Listing 8. The getGender() method, instead of return a character, returns a enum Gender, which can be either F or M, as in Listing 9. Even though the setGender(char) method signature is the same as before, gender for Member is no longer a JavaBean property for the type of return of getGender() is not the same as the type of parameter to the setGender() method. For this reason, a BeanPropertySetterRule will not work for this case. To still call the setGender() method on a Member object to set the gender of the member based on content of the nested <gender> element, we need to use the CallMethodRule. Below is the new code to replace Line 39 in Listing 7.

forPattern("family/member/gender")
        .callMethod("setGender").withParamCount(1)
        .withParamTypes("java.lang.Character")
        .then().callParam();

Listing 8 - A new Version of the Member class

package commons.digester3.example;

public class Member {
    private String firstname;
    private Gender gender;
    private int age;
    
    public String getFirstname() {
        return firstname;
    }
    
    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }
    
    public Gender getGender() {
        return this.gender;
    }
    
    public void setGender(char gender) {
        if (gender == 'F') {
            this.gender = Gender.F;
        } else if (gender == 'M') {
            this.gender = Gender.M;
        } else {
            throw new RuntimeException("Invalid gender code " + gender + ". It can only be 'F' or 'M'");
        }
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
}

Listing 9 - The Gender enum

package commons.digester3.example;

public enum Gender {
    F, M
}

Sunday, September 11, 2011

Mixing Scala and Java in a Project, Developed in Eclipse and Built by Maven

(Last updated on February 26, 2012)


 Overview

This is a tutorial showing how to setup a project (aka module) with both Scala and Java source code, to be built by Maven. In addition, this tutorial also shows how to setup the exact project in the Eclipse IDE so that developers can code and compile both Scala and Java source code in the same project.

If you only want to develop a mixed Java/Scala project in Eclipse, and you don't care about to build the project outside Eclipse using Maven and to manage Eclipse build path using Maven, you can do so using the Scala IDE for Eclipse out of box. 

Arguably, the largest advantage of the Java programming language over many other programming languages is the existence of countless libraries in Java.  Scala fully leverages this advantage of Java. Scala is very attractive for on one hand, it is a much more powerful programming language, and on the other hand, a Scala method can call almost any Java method as easy as another Java method does so. A Java method can also easily call a Scala method with certain limitations.

When develop a new project (as the term is used in the Eclipse IDE or the Maven build tool), it may be very desirable to have both Java and Scala classes in it. We may have some developers in the project who can only write Java code. We may also have to write some classes in Java in order to fit into certain frameworks or containers. Under those circumstances, a pure Scala project is not an option. If we still want to take advantage of the power of Scala, we have to mix Scala and Java in the same project. Being able to easily mix Scala and Java in the same project will greatly lower the obstacle for organizations to adopt Scala.

This tutorial is about the tricks in a Maven POM file to enable us to
  • Develop a project with both Scala and Java classes in Eclipse, where some Java methods call Scala methods, and some Scala methods call Java methods.
  • Use Maven to manage project build path in Eclipse
  • Build the project outside Eclipse using Maven
For a discussion on the advantages of using Maven to manage Eclipse project build path, see my other post.

Being able to build the project outside Eclipse using Maven makes it very easy to include that project into a larger project as a sub-project (called a module in Maven) later.

To follow this tutorial, you need:
  1. JDK 1.6 (not JDK 1.7)
  2. Eclipse Classic 3.6.x (not 3.7)
  3. The Scala IDE for Eclipse 2.0. The Scala IDE for Eclipse is an Eclipse plugin. If you need help in installing the plugin, please see the Appendix A at the end of this tutorial.
  4. Maven 2.2.1 or later
 In this tutorial, we develop a Hello World program consisting of:
  1. A Java class, GreetingInJava, with a method greet() which simply prints “Hello World!” to the console. It is normally the only class in a Java Hello World program.
  2. A Scala class GreetingInScala, with a method greet(), which instantiates a GreetingInJava instance, and call its greet() method. This is to show calling a Java method from a Scala method.
  3. A Java class, Bootstrap, with a main() method, in which a GreetingInScala instance is instantiated and the greet() method is called on that instance. This is to show calling a Scala method from a Java method.
Layout the Project

We are going to create the project outside Eclipse and to import it into Eclipse later. For more detailed about creating a project outside Eclipse and importing it into Eclipse, see my other post. Under C:\temp, create a directory structure as below.





Create Maven pom.xml

Create a pom.xml file under C:\temp\hello with the following content:



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

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/maven-v4_0_0.xsd">
    
    <modelVersion>4.0.0</modelVersion>
    <groupId>ted-gao</groupId>
    <artifactId>scala-java-mix</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>Scala-Java mixture</name>
    <description>Showcase mixing Scala and Java</description>
    <packaging>jar</packaging>
    
    <build>
        <plugins>
            <!-- ensure that we use JDK 1.6 -->
            <plugin>
                <inherited>true</inherited>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <version>2.15.2</version>
                <executions>
                    <!-- Run scala compiler in the process-resources phase, so that dependencies on
                         scala classes can be resolved later in the (Java) compile phase -->
                    <execution>
                        <id>scala-compile-first</id>
                        <phase>process-resources</phase>                        
                        <goals>
                            <goal>compile</goal>
                        </goals>
                        </execution>

                        <!-- Run scala compiler in the process-test-resources phase, so that dependencies on
                             scala classes can be resolved later in the (Java) test-compile phase -->                    
                        <execution>
                        <id>scala-test-compile</id>
                        <phase>process-test-resources</phase>
                        <goals>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <executions>
                    <!-- Add src/main/scala to source path of Eclipse -->
                    <execution>
                        <id>add-source</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>src/main/scala</source>
                            </sources>
                        </configuration>
                    </execution>
                      
                    <!-- Add src/test/scala to test source path of Eclipse -->
                    <execution>
                        <id>add-test-source</id>
                        <phase>generate-test-sources</phase>
                        <goals>
                            <goal>add-test-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>src/test/scala</source>
                            </sources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            
            <!-- to generate Eclipse artifacts for projects mixing Scala and Java -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.8</version>
                <configuration>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                    <projectnatures>
                        <projectnature>org.scala-ide.sdt.core.scalanature</projectnature>
                        <projectnature>org.eclipse.jdt.core.javanature</projectnature>
                    </projectnatures>
                    <buildcommands>
                        <buildcommand>org.scala-ide.sdt.core.scalabuilder</buildcommand>
                    </buildcommands>
                    <classpathContainers>
                        <classpathContainer>org.scala-ide.sdt.launching.SCALA_CONTAINER</classpathContainer>
                        <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer>
                    </classpathContainers>
                    <excludes>
                        <!-- in Eclipse, use scala-library, scala-compiler from the SCALA_CONTAINER rather than POM <dependency> -->
                        <exclude>org.scala-lang:scala-library</exclude>
                        <exclude>org.scala-lang:scala-compiler</exclude>
                    </excludes>
                    <sourceIncludes>
                        <sourceInclude>**/*.scala</sourceInclude>
                        <sourceInclude>**/*.java</sourceInclude>
                    </sourceIncludes>
                </configuration>
            </plugin>
            
            <!-- When run tests in the test phase, include .java and .scala source files -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.8.1</version>
                <configuration>
                    <includes>
                        <include>**/*.java</include>
                        <include>**/*.scala</include>
                    </includes>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>2.9.0</version>
        </dependency>
    </dependencies>
</project>

It is OK for Java classes/interfaces to depend on Scala classes/objects because Scala classes/objects are compiled before Java classes/interfaces. It is also OK for Scala classes/objects/traits to depend Java classes/interfaces because Scala classes/objects/traits can be compiled against both Java source code and bytecode.


Create Eclipse Artifacts

Run Maven Eclipse plugin to create Eclipse project artifacts and configure the Eclipse workspace with path variable M2-REPO pointing to the local Maven repository. If you need help to do those tasks, please see my other post.

Create Java and Scala Classes

In Eclipse, create the following two Java classes and one Scala class.

GreetingInJava is a simple Java class.

package mix.java.scala;

package mix.java.scala;

public class GreetingInJava {
    public void greet() {
        System.out.println("Hello World!");
    }
}

GreetingInScala is a simple Scala class that calls Java.

package mix.java.scala

class GreetingInScala {
    def greet() {
        val delegate = new GreetingInJava
        delegate.greet()
    }
}

Bootstrap is a simple Java class that calls Scala. If you need help to create new Scala classes in Eclipse, see the Appendix B at the end of this tutorial.

package mix.java.scala;

public class Bootstrap {

    public static void main(String[] args) {
        GreetingInScala scala = new GreetingInScala();
        scala.greet();
    }
}

Run Bootstrap.java in Eclipse

Right click Bootstrap.java in the source editor in Eclipse. From the context menu, select Run As... -> Java Application.

You will see string "Hello World!" is printed to the console.

Build the Project Outside Eclipse, Using Maven

Open a command line window and cd to C:\temp\hello, execute the following command:

mvn install

You will see Maven compile the three Java and Scala classes and package them into a jar file.

Run Bootstrap.java outside Eclipse


Open a command line window and cd to C:\temp\hello, if you have not done so. Execute the following command:


mvn exec:java -Dexec.mainClass=mix.java.scala.Bootstrap

You will see "Hello World!" is printed to the command line window.


Conclusion


With the pow.xml in this tutorial, we will be able to create projects with Java and Scala source code mixed. We will be able to develop both Java and Scala source code in Eclipse. We will also be able to run the Java and Scala classes in Eclipse. We will also be able to build the project and to execute the classes outside Eclipse using Maven.

Appendix A – Installing the Scala IDE for Eclipse

On the Eclipse workbench, select Help -> Install New Software ...



When the Install dialog box appears, click the Add button. When the Add Repository dialog box appears, fill in a name for the plugin and the URL of the update site for the Scala IDE for Eclipse, and click the OK button.


Back to the Install dialog box, check all available plugins as shown on the snapshot below.


When you see the following warning, ignore it and click the OK button.


When the installation is finished, you need to restart Eclipse.

Appendix B – Creating New Scala Class in the Scala IDE for Eclipse

On the Package Explorer view, right click the desired package. From the context menu, select New -> Other… When the New dialog box appears, select Scala Wizards -> Scala Class.

[If you are also interested in developing Scala programs using Eclipse and sbt (instead of Maven) together, please read Using Eclipse and sbt in Scala Programming.]

Monday, September 5, 2011

Using Maven to Manage Library Dependencies in Eclipse without m2eclipse

Eclipse has a built-in function managing library dependencies. By the usual practice, for each project (as the term means in Eclipse), one downloads the libraries (i.e. jar files) and places them under a directory name lib under the project base directory, and adds the jar files onto the project’s Build Path. This practice has the following disadvantages:
  • One has to figure out what jar files to download and where to place them. 
  • When a library itself depends on other libraries, one has to figure out what the dependencies are, to download them and to add them onto the project’s Build Path. And repeats recursively. 
  •  It is difficult to reuse part of a project’s Build Path for a new project since there is not dependency tree information about the Build Path accessible for Eclipse users 
  •  There is not standard way to share the Build Path in a team 
  •  It is difficult to manage version of the libraries.
A better option is to manage library dependencies of Eclipse projects using Maven. It brings the following advantages:
  • Maven will take care of the jar file downloading. And the downloaded jars will be placed in the local Maven repository with standard layout. 
  •  Maven will take care of transitive dependencies. 
  •  The (direct) dependencies are captured in Maven POM files, which can be easily kept in source control system and can be easily shared within a team. Besides, library versions can be explicitly specified in POM files.
This tutorial shows how to manage library dependencies in an example Eclipse project using Maven without m2eclipse. m2eclipse (now officially called Maven Integration for Eclipse) is a plugin in Eclipse to integrate with Maven. This tutorial, however, does not discuss m2eclipse.  It is assumed that the readers have already installed Maven (and Eclipse). The project is a Hello World program. It, however, uses logback, a third party logging library, to trace program execution into a log file. With a little knowledge about logback, we know that the project depends on the logback-classic library. So in our pom.xml, we are going to have the following dependency

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>0.9.29</version>
        </dependency>

(If you need help to find the exact groupId/artifactId/version, please read my post on that topic.)

In fact, logback-classic itself depends on logback-core and slf4j-api, but we do not have to do anything since Maven will take care of it. It is a huge benefit of letting Maven to manage library dependencies inside and outside Eclipse.

When Eclipse is launched, the user must select a workspace by selecting its base directory. If the directory does not exist, Eclipse will create a new workspace with that directory as the base directory. When a user creates a new java project, the user must select a directory as the base directory of the project. A project base directory can be either inside or outside the workspace base directory. The outside option is better. With this option, we decouple projects from workspace. Eclipse workspaces are not version compatible. That means that when a user upgrades his/her Eclipse from version 3.6 to 3.7, he/she has to create a new workspace, to pull the project source code out of the old workspace, and to place them into the new workspace. That work is cut down to the minimum when projects are decoupled from workspace.  In this tutorial, we place the project base directory outside the workspace base directory.

Creating and Configuring a Workspace
Under C:\temp, create a new directory called eclipseworkspace. Open a command line console, cd into C:\temp\eclipseworkspace, and execute the following command:

mvn -Declipse.workspace=. eclipse:configure-workspace

It creates a new workspace and a path variable called M2_REPO pointing to the local maven repository. The path variable will be used later on project Build Paths.

Creating a Project
Under C:\temp, create a new directory hello. Inside hello, create directories as below. 

Create a pom.xml under hello with content as following.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/maven-v4_0_0.xsd">
   
    <modelVersion>4.0.0</modelVersion>
    <groupId>ted-gao</groupId>
    <artifactId>dependency-tutorial</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>Dependency-Management-Tutorial</name>
    <description>Tutorial on managing dependencies in Eclipse using Maven</description>
    <packaging>jar</packaging>
   
    <build>
        <plugins>
            <!-- ensure that we use JDK 1.6 -->
            <plugin>
                <inherited>true</inherited>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>           
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>0.9.29</version>
        </dependency>
    </dependencies>

</project>

In a command line console, cd into hello, and execute the following command:

mvn eclipse:eclipse

It creates Eclipse artifacts for a project.

Please notice the following output from Maven during above command execution:

Downloading: http://repo1.maven.org/maven2/ch/qos/logback/logback-classic/0.9.29/logback-classic-0.9.29.pom
Downloaded: http://repo1.maven.org/maven2/ch/qos/logback/logback-classic/0.9.29/logback-classic-0.9.29.pom (13 KB at 38.9 KB/sec)
Downloading: http://repo1.maven.org/maven2/ch/qos/logback/logback-parent/0.9.29/logback-parent-0.9.29.pom
Downloaded: http://repo1.maven.org/maven2/ch/qos/logback/logback-parent/0.9.29/logback-parent-0.9.29.pom (14 KB at 27.0 KB/sec)
Downloading: http://repo1.maven.org/maven2/ch/qos/logback/logback-core/0.9.29/logback-core-0.9.29.pom
Downloaded: http://repo1.maven.org/maven2/ch/qos/logback/logback-core/0.9.29/logback-core-0.9.29.pom (7 KB at 14.5 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.6.1/slf4j-api-1.6.1.pom
Downloaded: http://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.6.1/slf4j-api-1.6.1.pom (3 KB at 6.2 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/slf4j/slf4j-parent/1.6.1/slf4j-parent-1.6.1.pom
Downloaded: http://repo1.maven.org/maven2/org/slf4j/slf4j-parent/1.6.1/slf4j-parent-1.6.1.pom (10 KB at 19.1 KB/sec)
Downloading: http://repo1.maven.org/maven2/ch/qos/logback/logback-classic/0.9.29/logback-classic-0.9.29.jar
Downloaded: http://repo1.maven.org/maven2/ch/qos/logback/logback-classic/0.9.29/logback-classic-0.9.29.jar (239 KB at 486.7 KB/sec)
Downloading: http://repo1.maven.org/maven2/ch/qos/logback/logback-core/0.9.29/logback-core-0.9.29.jar
Downloaded: http://repo1.maven.org/maven2/ch/qos/logback/logback-core/0.9.29/logback-core-0.9.29.jar (308 KB at 630.9 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.6.1/slf4j-api-1.6.1.jar
Downloaded: http://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.6.1/slf4j-api-1.6.1.jar (25 KB at 51.1 KB/sec)

It shows that even though we only specified that our project depends on logback-classic, Maven figured out that logback-classic depends on logback-core and slf4j-api and included them in our project. Besides, Maven downloaded all the three libraries from the Maven Central Repository.

Developing with Eclipse

Launch Eclipse. When asked to select a workspace, select C:\temp\eclipseworkspace.



From Eclipse Workbench, select the menu item File -> Import…



On the Import dialog box, select General -> Existing Projects into Workspace and click the Next button.



Still on the Import dialog box, browse to select C:\temp\hello as the project root directory, and click the Finish button.



Now the Eclipse Workbench should look like the snapshot below.



Let’s see how well Maven managed library dependencies for us. Right click the project dependency-tutorial in the Package Explorer view, from the context menu, select Properties. On the Properties for dependency-tutorial dialog box, select Java Build Path on the left pane, and the Libraries tab on the right pane. We can see that three libraries, viz. logback-classic, logback-core, and slf4j-api, have been added to the Java Build Path – Maven has done that for us. The three library jar files are in the local Maven repository. The path variable M2_REPO points to the base directory of the local Maven repository.



Now, in Eclipse, create a file called logback.xml under resources. It is a logback configuration file. Write the following content into logback.xml.

<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>dependency-tutorial.log</file>

    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
    </encoder>
  </appender>

  <root level="trace">
    <appender-ref ref="FILE" />
  </root>
</configuration>

Create a Java class with full name tutorial.dependency.HelloWorld.

package tutorial.dependency;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger("tutorial.dependecy.HelloWorld");

        logger.trace("Before saying hello.");
        System.out.println("Hello World!");
        logger.trace("After saying hello.");
    }
}
Run HelloWorld. We will see that Hello World is printed on the console. Refresh the dependency-tutorial project, we will see a new file named dependency-tutorial.log. Open it. It has the following content:

2011-09-06 21:54:26,163 TRACE [main] t.d.HelloWorld [HelloWorld.java:10] Before saying hello.
2011-09-06 21:54:26,166 TRACE [main] t.d.HelloWorld [HelloWorld.java:12] After saying hello.

That is the work done by logback.

Update Dependencies


If you need to change library dependencies later. Change the dependency specification in the pom.xml file (i.e. <dependencies>). Execute mvn eclipse:eclipse again. And right click the project in Eclipse, and select Refresh from the context menu.

[If you are also interested in using sbt to manage library dependencies in Eclipse for Scala programs, please read Using Eclipse and sbt in Scala Programming.]