Hands on practice of maven constructure using profile & local repository etc.

Last week, I mavenized an old java project, I think some skills can be applied to most of other projects, such as local repository, profile and include/exclude resources from .war package etc. so I write down this article for those who could need these skills as well in their maven projects.

In some projects, we are not able to find all .jar files using the standard maven repository, some are hosted on the their own repo, some you might want to host on your local repo. First you need to add the new repositories to your pom.xml by needs.

<repositories>
	<repository>
		<id>standard maven repo</id>
		<name>maven2</name>
		<url>http://repo1.maven.org/maven2/</url>
	</repository>
	<repository>
		<id>openscada repo</id>
		<name>OpenScada Releases</name>
		<url>http://repo.openscada.org/maven/releases</url>
	</repository>
	<repository>
		<id>my-local-repo</id>
		<url>file://${basedir}/repo</url>
	</repository>
</repositories>

The ${basedir} is relative to the folder where you put the pom.xml, you can directly use it without definition. In my case, I put the local repo to the project folder, you can also define your own local repo folder such like “C:\your_local_repo\”.

Next step open the command line, go to the folder which contains the .jar files that can not be found on the standard maven repo. Now convert them to the maven format. The converted .jars will be copied automatically to the “C:\your_local_repo\” directory

mvn deploy:deploy-file -Durl=file:///C:/your_local_repo/ -Dfile=ojdbc6-0.1.jar -DgroupId=com.yourdomain -DartifactId=ojdbc6 -Dpackaging=jar -Dversion=0.1 
mvn deploy:deploy-file -Durl=file:///C:/your_local_repo/ -Dfile=SomeUtils-0.1.jar -DgroupId=com.yourdomain -DartifactId=SomeUtils -Dpackaging=jar -Dversion=0.1 

Then you can use the local .jar files in your pom.xml

<dependencies>
	<dependency>
		<groupId>com.yourdomain</groupId>
		<artifactId>ojdbc6</artifactId>
		<version>0.1</version>
		<scope>runtime</scope>
	</dependency>
	<dependency>
		<groupId>com.yourdomain</groupId>
		<artifactId>SomeUtils</artifactId>
		<version>0.1</version>
	</dependency>
	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>servlet-api</artifactId>
		<version>${servlet-api.version}</version>
		<scope>provided</scope>
	</dependency>
	<dependency>
		<groupId>javax.servlet.jsp</groupId>
		<artifactId>jsp-api</artifactId>
		<version>${jsp-api.version}</version>
		<scope>provided</scope>
	</dependency>
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>${junit.version}</version>
		<scope>test</scope>
	</dependency>
<dependencies>

Please note that some .jar file such as servlet-api and jsp-api are provided by the apache tomcat, so you need to define the scope to exclude them from the .war package. The default scope is compile, will include the .jar file in every stage of the development. provided means it will only used for the compilation, the jdk or the container will provided the .jar for the runtime. test is almost the same as the provided, it will only needed for the test, will not included in the .war package. read more here.

In case you need a different web.xml in the .war package as the development environment, you can simply define a profile with different properties definition as follows. Leave the rest job to the maven-war-plugin. If you need for example Java 1.7 to compile your project, don’t forget to define the source in the maven-compiler-plugin.

<properties>
	<web.xml.path>src/main/webapp/WEB-INF/web.xml</web.xml.path>
</properties>
<profiles>
	<profile>
		<id>prod</id>
		<properties>
			<web.xml.path>src/main/webapp/WEB-INF/prod/web.xml</web.xml.path>
		</properties>
	</profile>
</profiles>
<build>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-war-plugin</artifactId>
			<version>2.6</version>
			<configuration>
				<webXml>${web.xml.path}</webXml>
				<packagingExcludes>WEB-INF/prod/**</packagingExcludes>
				<webappDirectory>target/webapp</webappDirectory>
			</configuration>
		</plugin>
		<plugin>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>3.1</version>
			<configuration>
				<source>1.7</source>
				<target>1.7</target>
			</configuration>
		</plugin>
	</plugins>
</build>

Difference between getResource() & getClassLoader().getResource() in Java

Assume you are using eclipse and have defined the source path and output path in the .classpath file.

	<classpathentry kind="src" path="src/test/java" output="target/test-classes" including="**/*.java"/>
	<classpathentry kind="src" path="src/test/resources" output="target/test-classes" excluding="**/*.java"/>
	<classpathentry kind="src" path="src/main/java" including="**/*.java"/>
	<classpathentry kind="src" path="src/main/resources" excluding="**/*.java"/>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
	<classpathentry kind="output" path="target/classes"/>

Note that the resource are compiled java classes which are located in the “target/classes/” folder after successful compilation.

First I defined a Java Class src/main/java/com/test/FilePath.java and an empty src/main/resources/com/test/test.txt file that will be used for the following experiment. Which very interesting is that the .txt file will also be “compiled” and copied the the target folder when its content is changed and saved.

package com.test;

public class FilePath {
  private java.net.URL url = null;

  public String getPath() {
    return url != null ? url.getPath() : "";
  }

  public void setURL(final java.net.URL url) {
    this.url = url;
  }
}

Now I am trying to test the two similar methods using following Junit test cases:

package com.test;

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class FilePathTest {
  FilePath fp = new FilePath();
  Class<FilePath> clazz = FilePath.class;

  @Test
  public void testGetInputFilePath1() {
    fp.setURL(clazz.getResource(""));
    assertEquals("/D:/workspace/Test/target/test-classes/com/test/", fp.getPath());
  }

  @Test
  public void testGetInputFilePath2() {
    fp.setURL(clazz.getClassLoader().getResource(""));
    assertEquals("/D:/workspace/Test/target/test-classes/", fp.getPath());
  }

  @Test
  public void testGetInputFilePath3() {
    fp.setURL(clazz.getResource("FilePath.class"));
    assertEquals("/D:/workspace/Test/target/classes/com/test/FilePath.class", fp.getPath());
  }

  @Test
  public void testGetInputFilePath4() {
    fp.setURL(clazz.getClassLoader().getResource("com/test/FilePath.class"));
    assertEquals("/D:/workspace/Test/target/classes/com/test/FilePath.class", fp.getPath());
  }

  @Test
  public void testGetInputFilePath5() {
    fp.setURL(clazz.getResource("test.txt"));
    assertEquals("/D:/workspace/Test/target/classes/com/test/test.txt", fp.getPath());
  }

  @Test
  public void testGetInputFilePath6() {
    fp.setURL(clazz.getClassLoader().getResource("com/test/test.txt"));
    assertEquals("/D:/workspace/Test/target/classes/com/test/test.txt", fp.getPath());
  }
}

All the test cases are successful executed! It means the class.getClassLoader.getResource() method need the package path (com/test/) to find the corresponding resources, and the class.getResource() method uses already by default the package path to find the resource under the same folder without explicitly pointing this out.

But I am also quite sure that there is sometimes problem with the java compiler under the eclipse. So in case you can still not find the target file, say getResource() method always return NULL, which was occasionally also happen to me, please check whether the target file was copied to the class path (commonly “bin” or “target/class”) by the compiler. If not, please delete all your class files and recompile your project properly.

For a better understanding, feel free to extend the unit tests above to do more experiments of your own.