16
Feb
13

Log4j2 Configuration with Multiple Web Apps

This page describes how to use the new Log4j 2 Framework within multiple web applications deployed to a single web container with a shared log4j jar.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Background

Historically Java Web Application WAR files have included their own copy of the log4j jar files. This allowed multiple applications to co exist and use their own log4j configuration. However as web applications get smaller and focused smaller tasks (possibly RESTful applications) you will find yourself deploying multiple WAR files to the same web container. It doesnt make too much sense to have each WAR contain its own copy of log4j.jar.

Ideally it would be nice to have one log4j.jar file at the container level that each application can share. However, its not as simple as moving it out of the WAR file because of the way log4j configures itself.

This page covers a method described in the log4j 2 homepage.

Place the logging jars in the container’s classpath and use the default ClassLoaderContextSelector. Include the Log4jContextListener in each web application. Each ContextListener can be configured to share the same configuration used at the container or they can be individually configured. If status logging is set to debug in the configuration there will be output from when logging is initialized in the container and then again in each web application.

Requirements

  • Java 5 or later
  • Maven 2 or later
  • Log4j 2
  • Apache Tomcat 6

Procedure

We will create 2 apps and deploy them on Tomcat and verify that they are both logging to the console as well as individual files specified in their configs.

Any one who worked with log4j 1.x knows that this was a pain to setup with multiple web applications. Log4j 2 makes it a lot easier.

  • web-log-test – outputs to SYSOUT and ${user.home}/APPBASE/logs/web-log-test.log
  • web-log-test2 – outputs to SYSOUT and ${user.home}/APPBASE/logs/web-log-test2.log

Project Configuration

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.test</groupId>
  <artifactId>web-log-test</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  
	<pluginRepositories>
	  <pluginRepository>
	    <id>numberformat-releases</id>
	    <url>https://raw.github.com/numberformat/20130213/master/repo</url>
	  </pluginRepository>
	</pluginRepositories>
	  
  <dependencies>
  	<dependency>
  		<groupId>javax.servlet</groupId>
  		<artifactId>servlet-api</artifactId>
  		<version>2.5</version>
  		<scope>provided</scope>
  	</dependency>
  	<dependency>
  		<groupId>org.apache.logging.log4j</groupId>
  		<artifactId>log4j-core</artifactId>
  		<version>2.0-beta4</version>
  		<scope>provided</scope>
  	</dependency>
  	<dependency>
  		<groupId>org.apache.logging.log4j</groupId>
  		<artifactId>log4j-api</artifactId>
  		<version>2.0-beta4</version>
  		<scope>provided</scope>
  	</dependency>
  	<dependency>
  		<groupId>org.apache.logging.log4j.adapters</groupId>
  		<artifactId>log4j-web</artifactId>
  		<version>2.0-beta4</version>
  		<scope>provided</scope>
  	</dependency>
  	<dependency>
  		<groupId>org.apache.logging.log4j.adapters</groupId>
  		<artifactId>log4j-1.2-api</artifactId>
  		<version>2.0-beta4</version>
  		<scope>provided</scope>
  	</dependency>
  </dependencies>
  
	<build>
		<finalName>web-log-test</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.0.2</version>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
	 		<plugin> 			
	 			<groupId>github.numberformat</groupId>
	 			<artifactId>blog-plugin</artifactId>
	 			<version>1.0-SNAPSHOT</version>
	 			<configuration>
				<gitUrl>https://github.com/numberformat/20130216</gitUrl>
	 			</configuration>
	        <executions>
	          <execution>
	            <id>1</id>
	            <phase>site</phase>
	            <goals>
	              <goal>generate</goal>
	            </goals>	            
	          </execution>
	        </executions>
	 		</plugin>
		</plugins>
	</build>  
</project>

Web Configuration

The following sets up the log4jContextListener by specifying the log4jConfiguration parameter pointing it to the location of your config.

You may specify a file in the classpath or an absolute location on your system. If you want to keep this file in your WEB-INF folder then you can extend the class and customize it to suit your requirement.

vi src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	<display-name>Sample Web Application</display-name>

	<context-param>   
	   <param-name>log4jConfiguration</param-name>
	   <param-value>log4j2.xml</param-value>
	</context-param>
	
	<listener>
		<listener-class>org.apache.logging.log4j.core.web.Log4jContextListener</listener-class>
	</listener>

</web-app>

Log4j Configuration

In log4j 2 the name of the config file should be log4j2.xml. For this page our configuration file is very simple. See the log4j 2 site for further details about other configuration options.

vi src/main/resources/log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration strict="true">
<!-- include this to enable log4j internal debug messages: status="debug" -->
<appenders>
	<appender type="Console" name="STDOUT">
		<layout type="PatternLayout" pattern="%d %-5p %c - %m%n"/>
	</appender>
	<appender type="File" name="File" fileName="${sys:user.home}/APPBASE/logs/web-log-test.log">
		<layout type="PatternLayout">
			<pattern>%d %-5p %c - %m%n</pattern>
		</layout>
	</appender>
</appenders>
 
<loggers>
	<logger name="org.apache.jsp" level="debug">
		<appender-ref ref="File"/>
	</logger>
	<root level="trace">
		<appender-ref ref="STDOUT"/>
	</root>
</loggers>
 
</configuration>

Test Page

We can test the application by using a simple jsp.

vi src/main/webapp/snoop.jsp

<HTML>
<HEAD>
	<TITLE>JSP snoop page</TITLE>
	<%@ page import="org.apache.log4j.*" %> 
</HEAD>
<BODY>

<H1>WebApp JSP Log4j Test</H1>

<h3>Apache Log4j Logging</h3>

<%
Logger logger = Logger.getLogger(getClass());
if("submit".equals((String)request.getParameter("log"))) {
	String text = (String)request.getParameter("text");
	String level = (String)request.getParameter("level");
	logger.log(Level.toLevel(level), text);
}
%>

<form action="" method="GET">
<TABLE border="1">
<TR valign=top>
	<TH align=left>Level</TH>
	<TH align=left>Test Message</TH>
</TR>
<TR valign=top>
	<TD>
		<select name="level">		
			<option value="ERROR">ERROR</option>
			<option value="WARN">WARN</option>
			<option value="INFO" selected="selected">INFO</option>
			<option value="DEBUG">DEBUG</option>
			<option value="TRACE">TRACE</option>
		</select>
	</TD>
	<TD>
		<input name="text" type="text" size="35" value="<%=new java.util.Date()%>">
		<input name="log" type="submit" value="submit">	
	</TD>
</TR>
</TABLE>
</form>

</BODY>
</HTML>

Finally copy the following files to the “tomcat6/lib” folder. You may obtain these files from the .m2 folder after the project is built. Or you can get them from the apache download site. (at the time of this writing only beta4 was the latest available)

  • log4j-core-2.0.jar – core libarary.
  • log4j-api-2.0.jar – API Shell Classes to be used by your code
  • log4j-web-2.0.jar – Servlet Listener to initialize Logging
  • log4j-1.2-api-2.0.jar – log4j 1.x support for legacy code that still uses it.

Build and Run the Application

perform the build by typing:

mvn clean compile package

You will notice that the WAR file does not contain any log4j jars.

Copy the war file into your tomcat6/webapps folder.

navigate to: http://localhost:8080/web-log-test/snoop.jsp

Modify and Deploy the Second Application

For the second application we want to modify the log4j2.xml to write the log to ${user.home}/APPBASE/logs/web-log-test2.log

Rename the WAR file to web-log-test2.war deploy to the same server.

Restart and test.

Using a new TAB, navigate to: http://localhost:8080/web-log-test2/snoop.jsp

Verify that the logs are being written the console and to each file independently.

Appendix

log4j 1.x configuration available here

Full downloadable source for this page is available here.
13
Feb
13

Create Custom Maven Plugin

This page describes how to create a custom maven plugin.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Problem Statement

This site contains hundreds pages with small demo java projects embedded in its pages.

While working on this site I came across a few problems.

  1. How to reduce typo errors on the pages source code.
  2. How to keep the blog page up to date after code fixes.

The solution: Have a custom maven plugin generate the blog page with source code automatically. The plugin uses velocity template that contain the text of the blog page along with “#include” velocity directives that bring in the source code from the project.

Implementation

Follow these steps to create a custom plugin to generate HTML content from velocity templates.

Start by editing the pom.xml file to look like this.

vi pom.xml

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

	<groupId>github.numberformat</groupId>
	<artifactId>blog-plugin</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>maven-plugin</packaging>

	<properties>
		<mavenVersion>2.0.6</mavenVersion>
	</properties>

	<pluginRepositories>
	  <pluginRepository>
	    <id>numberformat-releases</id>
	    <url>https://raw.github.com/numberformat/20130213/master/repo</url>
	  </pluginRepository>
	</pluginRepositories>

	<dependencies>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-plugin-api</artifactId>
			<version>2.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.velocity</groupId>
			<artifactId>velocity</artifactId>
			<version>1.7</version>
		</dependency>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-model</artifactId>
			<version>${mavenVersion}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-artifact</artifactId>
			<version>${mavenVersion}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-project</artifactId>
			<version>${mavenVersion}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-core</artifactId>
			<version>${mavenVersion}</version>
		</dependency>
	</dependencies>
	<name>Wordpress Page Generation Plugin</name>
	<description>Generates a wordpress page for the project.</description>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.0.2</version>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
	 		<plugin> 			
	 			<groupId>github.numberformat</groupId>
	 			<artifactId>blog-plugin</artifactId>
	 			<version>1.0-SNAPSHOT</version>
	 			<configuration>
				<gitUrl>https://github.com/numberformat/20130213</gitUrl>
	 			</configuration>
	        <executions>
	          <execution>
	            <id>1</id>
	            <phase>site</phase>
	            <goals>
	              <goal>generate</goal>
	            </goals>	            
	          </execution>
	        </executions>
	 		</plugin>
		</plugins>
	</build>
</project>

The following file is the plugin implementation class.

BlogMojo.java

package github.numberformat.plugin;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;

/**
 * This is a simple plug-in that generates a blog page from a velocity
 * template. This allows developers to create very simple project documentation
 * pages for their projects. Developers would typically create a blog entry by
 * copying and pasting the HTML directly into the blog site.
 * 
 * @goal generate
 */
public class BlogMojo extends AbstractMojo {
	/**
	 * @parameter default-value="${basedir}"
	 * @required
	 * @readonly
	 */
	private File basedir;

	/**
	 * Represents the date first published.
	 * 
	 * @parameter default-value="https://github.com/numberformat"
	 * @required
	 * @readonly
	 */
	private String gitUrl;
	
	
	public void execute() throws MojoExecutionException {
		
		final File templateDir = basedir;
		final File targetBlog = new File(basedir, "target/blog");

		if(new File(templateDir, "src/blog/wordpress.vm").canRead()) {
			if(!targetBlog.exists()) {
				targetBlog.mkdirs();
			} else if(!targetBlog.isDirectory()) {
				throw new MojoExecutionException("Must be a directory: " + targetBlog.getAbsolutePath());
			}
	
	        VelocityEngine ve = new VelocityEngine();
	        ve.setProperty("file.resource.loader.path", templateDir.getAbsolutePath());
	        ve.init();
        
	        Template t = ve.getTemplate( "src/blog/wordpress.vm" );
	        VelocityContext context = new VelocityContext();
	        context.put("blog_header", getHeader());
	        context.put("blog_footer", getFooter());
	        context.put("blog_git_url", gitUrl);
	        
	        FileWriter writer = null;
			try {
				writer = new FileWriter(new File(targetBlog, "wordpress.html"));
		        t.merge( context, writer );				
			} catch (IOException e) {
				throw new MojoExecutionException(e.getMessage());
			} finally {
				try{writer.close();}catch(Exception e){}				
			}
		}
	}


	private Object getFooter() {
		return "<div style=\"font-size:13px;border:1px solid gray; " +
				"padding:5px;line-height:120%\">Full downloadable source for " +
				"this page is <a href=\""+gitUrl+"\">available here</a>. " +
				"</div>";
	}

	private Object getHeader() {
		return "<div style=\"font-size:13px;border:1px solid gray; " +
				"padding:5px;line-height:120%\">Full downloadable source for " +
				"this page is <a href=\""+gitUrl+"\">available here</a>. " +
				"Corrections and enhancements are welcome, fork, change and push " +
				"back to GitHub.</div>";
	}
}

Publish to Nexus or Website

You may deploy the plugin into a nexus repository a simple website.

Example Usage

For demonstration purposes I have published the plugin to the following URL, you may use it in your project by including it in your pom.xml.

vi pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.test</groupId>
  <artifactId>testProject</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <pluginRepositories>
    <pluginRepository>
      <id>numberformat-releases</id>
      <url>https://raw.github.com/numberformat/20130213/master/repo</url>
    </pluginRepository>
  </pluginRepositories>
  
  <build>
  	<plugins>
  		<plugin> 			
  			<groupId>github.numberformat</groupId>
  			<artifactId>blog-plugin</artifactId>
  			<version>1.0-SNAPSHOT</version>
	        <executions>
	          <execution>
	            <id>1</id>
	            <phase>site</phase>
	            <goals>
	              <goal>generate</goal>
	            </goals>	            
	          </execution>
	        </executions>
  		</plugin>
  	</plugins>
  </build>
</project>

As you can see from the above the plugin to generate the wordpress page is hooked into the “site” phase of the build lifecycle.

Velocity Template

Save the file into:

src/blog/wordpress.vm

#set( $foo = "Velocity" )
Hello $foo World!

(include sourcecode tag in square brackets around the include line below)
#include("src/main/java/App.java")
(include /sourcecode tag in square brackets around the include line above)

Run the Plugin

To run the plugin and you have specified the executions tag above just type

mvn site

As an alternative if you don’t want to hook it into the maven lifecycle then just delete the “executions” tag above and run the plugin by typing:

To run the plugin just type

mvn blog:generate

If you get a WARNING about plexus ignore it.

Upon successful build you can view the generated wordpress page in the target/blog folder of the project.

Next Steps

blog-plugin improvements:

  1. Enhance the plugin to generate pages in formats other than wordpress.
  2. Have plugin insert headers or footers on the generated pages. (done)
  3. Enhance the plugin to have blog pages contain a link to GitHub where visitors can simply checkout the project instead of copying and pasting source from the page. (done)
Full downloadable source for this page is available here.
09
Feb
13

Convert UTF-8 Unicode to ASCII Latin 1

This page describes how to convert utf-8 or Unicode strings with diacritical characters or Unicode punctuation marks into Latin 1 encoding with minimal loss of information.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

There is a 2 step process to get this done.

Step 1

Use java.text.Normalizer to convert diacritical characters with accents into ASCII characters.

str = Normalizer.normalize(str, Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "");

Step 2

Convert Symbols and punctuation marks into latin-1 equivalents.

Currently the best way to get this done is to search and replace as seen below.

Sample Swing App

The following app demonstrates how to convert Unicode to ASCII latin-1.

vi src/main/java/github/numberformat/utf/Norm.java

package github.numberformat.utf;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Label;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.Normalizer;
import java.util.HashMap;
import java.util.Iterator;

import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Norm extends JFrame {
	private static final long serialVersionUID = 1L;

	private JComboBox normalizationTemplate;
	private JComboBox formComboBox;
	private JComponent paintingComponent;
	private HashMap<String, Normalizer.Form> formValues = new HashMap<String, Normalizer.Form>();
	private HashMap<String, String> templateValues = new HashMap<String, String>();

	public Norm() {
		init();
	}
	
	public void init() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setTitle("UTF-8 to ASCII");
		
		formValues.put("NFC", Normalizer.Form.NFC);
		formValues.put("NFD", Normalizer.Form.NFD);
		formValues.put("NFKC", Normalizer.Form.NFKC);
		formValues.put("NFKD", Normalizer.Form.NFKD);
		
		formComboBox = new JComboBox();
		for (Iterator it = formValues.keySet().iterator(); it.hasNext();) {
			formComboBox.addItem((String) it.next());
		}
		templateValues.put("acute accent", "\u2039touch" + "\u00e9\u2035");

		// text with ligature
		templateValues.put("ligature", "a" + "\ufb03" + "ance");

		// text with the cedilla
		templateValues.put("cedilla", "fa" + "\u00e7" + "ade");

		
		templateValues.put("half-width katakana",
				"\uff81\uff6e\uff7a\uff9a\uff70\uff84");

		normalizationTemplate = new JComboBox();

		for (Iterator it = templateValues.keySet().iterator(); it.hasNext();) {
			normalizationTemplate.addItem((String) it.next());
		}
		
		JPanel controls = new JPanel();

		controls.setLayout(new BoxLayout(controls, BoxLayout.X_AXIS));
		controls.add(new Label("Normalization Form: "));
		controls.add(formComboBox);
		controls.add(new Label("Normalization Template:"));
		controls.add(normalizationTemplate);
		formComboBox.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				paintingComponent.repaint();
			}
		});

		normalizationTemplate.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				paintingComponent.repaint();
			}
		});

		
		getContentPane().add(getCenter(), BorderLayout.CENTER);
		getContentPane().add(controls, BorderLayout.SOUTH);
		pack();
		setVisible(true);

	}

	private JComponent getCenter() {
		if(paintingComponent != null) return paintingComponent;
		
		paintingComponent = new JComponent() {
			static final long serialVersionUID = -3725620407788489160L;

			public Dimension getSize() {
				return new Dimension(550, 200);
			}

			public Dimension getPreferredSize() {
				return new Dimension(550, 200);
			}

			public Dimension getMinimumSize() {
				return new Dimension(550, 200);
			}

			public void paint(Graphics g) {
				Graphics2D g2 = (Graphics2D) g;

				g2.setFont(new Font("Serif", Font.PLAIN, 20));
				g2.setColor(Color.BLACK);
				g2.drawString("Original string:", 100, 80);
				g2.drawString("Normalized string:", 100, 120);
				g2.setFont(new Font("Serif", Font.BOLD, 24));

				// output of the original sample selected from the ComboBox

				String original_string = templateValues
						.get(normalizationTemplate.getSelectedItem());
				g2.drawString(original_string, 320, 80);

				// normalization and output of the normalized string

				String normalized_string = utf8ToLatin1(original_string);

				g2.drawString(normalized_string, 320, 120);
			}

			private String utf8ToLatin1(String original_string) {
				String normalized_string;
				java.text.Normalizer.Form currentForm = formValues
						.get(formComboBox.getSelectedItem());
				normalized_string = Normalizer.normalize(original_string,
						currentForm);

				normalized_string = normalized_string.replaceAll(
						"\\p{InCombiningDiacriticalMarks}+", "");

				String str = normalized_string;
				str = str
						.replaceAll(
								"[\u00AB\u2034\u2037\u00BB\u02BA\u030B\u030E\u201C\u201D\u201E\u201F\u2033\u2036\u3003\u301D\u301E]",
								"\"");
				str = str.replaceAll("[\u02CB\u0300\u2035]", "`");
				str = str.replaceAll("[\u02C4\u02C6\u0302\u2038\u2303]", "^");
				str = str.replaceAll("[\u02CD\u0331\u0332\u2017]", "_");
				str = str.replaceAll(
						"[\u00AD\u2010\u2011\u2012\u2013\u2014\u2212\u2015]",
						"-");
				str = str.replaceAll("[\u201A]", ",");
				str = str.replaceAll("[\u0589\u05C3\u2236]", ":");
				str = str.replaceAll("[\u01C3\u2762]", "!");
				str = str.replaceAll("[\u203D]", "?");
				str = str
						.replaceAll(
								"[\u00B4\u02B9\u02BC\u02C8\u0301\u200B\u2018\u2019\u201B\u2032]",
								"'");
				str = str.replaceAll("[\u27E6]", "[");
				str = str.replaceAll("[\u301B]", "]");
				str = str.replaceAll("[\u2983]", "{");
				str = str.replaceAll("[\u2984]", "}");
				str = str.replaceAll("[\u066D\u204E\u2217\u2731]", "*");
				str = str.replaceAll("[\u00F7\u0338\u2044\u2060\u2215]", "/");
				str = str.replaceAll("[\u20E5\u2216]", "\\");
				str = str.replaceAll("[\u266F]", "#");
				str = str.replaceAll("[\u066A\u2052]", "%");
				str = str.replaceAll("[\u2039\u2329\u27E8\u3008]", "<");
				str = str.replaceAll("[\u203A\u232A\u27E9\u3009]", ">");
				str = str.replaceAll("[\u01C0\u05C0\u2223\u2758]", "|");
				str = str.replaceAll("[\u02DC\u0303\u2053\u223C\u301C]", "~");
				normalized_string = str;
				return normalized_string;
			}
		}; 
				
				
		return paintingComponent;
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new Norm();
	}

}

To run the app just type the following:

mvn exec:java -Dexec.mainClass=github.numberformat.utf.Norm
Full downloadable source for this page is available here.
07
Nov
12

JAX-WS Hello World Standalone / Tomcat / JBoss

The following page describes how to create a simple JAX-WS web service and run it under JBoss and Tomcat and as a standalone App.

Background

JAX-WS supports both traditional SOAP-RPC as well as the newer RESTful/JSON type services. Its a plug and play framework where the developer annotate their classes and the application server takes care of the rest.

Requirements

  1. Java 6 or above
  2. Tomcat 6 or JBoss 5
  3. Maven 3

Interface

vi src/main/java/server/HelloWorld.java

package server;

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;

@WebService
@SOAPBinding(style = Style.RPC)
public interface HelloWorld {
@WebMethod String getHelloWorldAsString(String name);
}

Implementation

vi src/main/java/server/HelloWorldImpl.java

package server;

import javax.jws.WebService;
import javax.xml.ws.Endpoint;

@WebService(endpointInterface = "server.HelloWorld")
public class HelloWorldImpl implements HelloWorld {
	@Override
	public String getHelloWorldAsString(String name) {
		return "Hello World JAX-WS " + name;
	}
	public static void main(String args[]) { // for test purposes only
		Endpoint.publish("http://localhost:9999/ws/hello", new HelloWorldImpl());		
	}	
}

Client Application

If you would like to test without generating the client application you can do the following.

vi HelloWorldApp.java

import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

import server.HelloWorld;

public class HelloWorldApp {
	
	public static void main(String args[]) throws Exception {
		URL url = new URL("http://localhost:9999/ws/hello?wsdl");
		QName qname = new QName("http://server/", "HelloWorldImplService");	
		Service service = Service.create(url, qname);
                addDebugSupport(service); // optional (see method below)
		HelloWorld hello = service.getPort(HelloWorld.class);
		System.out.println(hello.getHelloWorldAsString("name"));
	}
}

The following method allows for the incoming and outgoing SOAP messages to be logged. It requires HTMLTidy dependency

    <dependency>
        <groupId>jtidy</groupId>
        <artifactId>jtidy</artifactId>
        <version>4aug2000r7-dev</version>
    </dependency>
private static void addDebugSupport(Service service) {
	service.setHandlerResolver(new HandlerResolver() {
		public List<Handler> getHandlerChain(PortInfo portInfo) {
			List<Handler> handlerChain = new ArrayList<Handler>();
			handlerChain.add(new SOAPHandler<SOAPMessageContext>() {
				public boolean handleFault(SOAPMessageContext context) {
					log(context);
					return true;
				}
				public boolean handleMessage(SOAPMessageContext context) {
					log(context);
					return true;
				}
				public void close(MessageContext context) {}
				public Set<QName> getHeaders() {return null;}
			});
			return handlerChain;
		}
		private void log(SOAPMessageContext smc) {
			Boolean outboundProperty = (Boolean) smc
					.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
			if(outboundProperty.booleanValue()) {
				logger.info("Outbound message:");					
			} else {
				logger.info("Inbound message:");
			}
			SOAPMessage message = smc.getMessage();
			try {
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				message.writeTo(baos);
				ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
				Tidy tidy = new Tidy();
				tidy.setXmlOut(true);
				tidy.setXmlTags(true);
				baos = new ByteArrayOutputStream();
				tidy.parse(bais, baos);
				logger.info("\n" + new String(baos.toByteArray()));
			} catch (Exception e) {
				logger.error("Exception in handler: " + e);
			}
		}
    });
}

Package and Deploy the JAX-WS Web Service

Create a sun-jaxws.xml file if you are planning to use the Reference Implementation. Otherwise skip it.

src/main/webapp/WEB-INF/sun-jaxws.xml

<?xml version="1.0" encoding="UTF-8"?>
<endpoints
  xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
  version="2.0">
  <endpoint
      name="HelloWorld"
      implementation="server.HelloWorldImpl"
      url-pattern="/hello"/>
</endpoints>

The following is the way the web.xml file should look if you are using the Reference Implementation. Otherwise just skip to the JBoss section.

src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, 
Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
 
<web-app>
    <listener>
        <listener-class>
                com.sun.xml.ws.transport.http.servlet.WSServletContextListener
        </listener-class>
    </listener>
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>
        	com.sun.xml.ws.transport.http.servlet.WSServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>120</session-timeout>
    </session-config>
</web-app>

Run under Tomcat

Tomcat 6 does not include the JAX-WS RI jars, instead download them from oracle and place them in the tomcat6/lib directory of your server or your WEB-INF/lib folder or the WAR file.

The following is a sample list: your version of JAX-WS can vary.

jaxb-impl.jar
jaxws-api.jar
jaxws-rt.jar
gmbal-api-only.jar
management-api.jar
stax-ex.jar
streambuffer.jar
policy.jar

Run under JBoss 5

JBoss comes pre-installed with a JAX-WS implementation. No need to package the JAX-WS implementation in the war. Instead just put the following in the web.xml file and navigate to the following URL:

http://127.0.0.1:8080/jaxwsTest/hello?wsdl

 <servlet>
  <servlet-name>HelloService</servlet-name>
  <servlet-class>server.HelloWorldImpl</servlet-class>
 </servlet>
 <servlet-mapping>
  <servlet-name>HelloService</servlet-name>
  <url-pattern>/hello</url-pattern>
 </servlet-mapping>

JAX-WS Web Service Client

To create a standalone client application use wsimport command provided by the JDK 6.

Appendix: MockServer

The following is an implementation of a mock server that could be used for testing.

package service;

import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.ws.Endpoint;

public class MockServer {

	private static final MockServer instance = new MockServer();
	
	private Map<QName, String> urlMap = new HashMap<QName, String>();
	
	private MockServer() {
		super();
		publish("DataManagerImplService", "http://service/", getAvailablePort(), new DataManagerImpl());
	}

	private void publish(String serviceName, String uri, int port, Object implObject) {
		String url = "http://localhost:" + port + "/ws/" + serviceName + "?wsdl";
		QName qname = new QName(uri, serviceName);
		Endpoint.publish(url, implObject);
		urlMap.put(qname, url);
	}
	
	public static MockServer getInstance() {
		return instance;
	}

	public URL getUrl(QName qname) {
		URL url = null;
		try {
			url = new URL(urlMap.get(qname));
		} catch (MalformedURLException e) {
		}
		return url;
	}
	
	private static int getAvailablePort() {
		int count = 0;
		int port = 0;
		while (true) {
			try {
				ServerSocket s = new ServerSocket(0);
				port = s.getLocalPort();
				s.close();				
				break;			
			} catch (Exception ex) {
				if (count++ > 3) throw new RuntimeException(ex.getMessage());
			}
		}
		return port;
	}
	
}
08
Oct
12

Java Compiler API

This page describes a sample application to compile a class file from a String that is pasted in.

package com.test;

import java.awt.Component;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class TestApp extends JFrame {

	private Container c;
	JTextArea txtSource;
	
	public TestApp() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        c = this.getContentPane();
        setLayout(new FlowLayout());
        this.setTitle("Class Loader Test App");
        txtSource = getTextArea();
        c.add(txtSource);
        c.add(compileButton());
        c.add(executeButton());
        this.pack();
        this.setVisible(true);        
	}
	
	private Component executeButton() {
		JButton executeButton = new JButton("execute");
		executeButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
	            try {
					new URLClassLoader(new URL[] { new File("").toURI()
							.toURL() }).loadClass("Test").newInstance();
	            } catch (Exception ex) {
	            	ex.printStackTrace();
	            }
			}
		});
		return executeButton;
	}

	private JButton compileButton() {
		JButton btnCompile = new JButton("Compile");
		
		btnCompile.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.out.println("Compiling...");
				
				compile(txtSource.getText());
			}
			SimpleJavaFileObject getJavaFileContentsAsString() {
				StringBuilder javaFileContents = new StringBuilder(txtSource.getText());
				JavaObjectFromString javaFileObject = null;
				try {
					javaFileObject = new TestApp.JavaObjectFromString("Test.java",
							javaFileContents.toString());
				} catch (Exception exception) {
					exception.printStackTrace();
				}
				return javaFileObject;
			}
			private void compile(String text) {
				JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
				DiagnosticCollector diagnosticsCollector = new DiagnosticCollector();
				StandardJavaFileManager fileManager  =
					compiler.getStandardFileManager(diagnosticsCollector, null, null);
				
				Iterable fileObjects = null;
				boolean fromFile = false;
				if(fromFile) {
					String fileToCompile = "Test.java";
					fileObjects = fileManager.getJavaFileObjectsFromStrings(Arrays.asList(fileToCompile)); // Line 5
				} else {
					JavaFileObject javaObjectFromString = getJavaFileContentsAsString();
					fileObjects = Arrays.asList(javaObjectFromString);
				}
				
				CompilationTask task = compiler.getTask(null, fileManager, diagnosticsCollector, null, null, fileObjects);
				
		        Boolean result = task.call(); // Line 7
		        
		        List<Diagnostic> diagnostics = diagnosticsCollector.getDiagnostics();
		        
		        for(Diagnostic d : diagnostics){
		            // Print all the information here.
		        	System.out.println(d.getMessage(null));
		        }
		        
		        if(result == true){
		            System.out.println("Compilation is successful");
		        }else{
		            System.out.println("Compilation Failed");
		        }
			}
		});
		return btnCompile;
	}

	static class JavaObjectFromString extends SimpleJavaFileObject {
		private String contents = null;

		public JavaObjectFromString(String className, String contents)
				throws Exception {
			super(new URI(className), Kind.SOURCE);
			this.contents = contents;
		}

		public CharSequence getCharContent(boolean ignoreEncodingErrors)
				throws IOException {
			return contents;
		}
	}

	
	private JTextArea getTextArea() {
		JTextArea txtSource = new JTextArea(25, 80);
		return txtSource;
	}

	public static void main(String args[]) {
		new TestApp();
	}
}
19
Jun
12

Internationalizing a GWT Application

This page describes how to setup internationalisation in your GWT application. The example on this page builds from the page listed in the requirements section.

Requirements

Project Configuration

In order to enable internationalisation support you need to add the following “goal” and “i18nMessagesBundle” elements inside “gwt-maven-plugin”:

vi pom.xml

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>gwt-maven-plugin</artifactId>
        <version>2.4.0</version>
        <executions>
          <execution>
            <goals>
            ...
            <goal>i18n</goal>
            ...
            </goals>
...

insert the “i18nMessagesBundle” element here:

        <configuration>
          ...
          <i18nMessagesBundles>
                    <i18nMessagesBundle>com.test.client.Messages</i18nMessagesBundle>
          </i18nMessagesBundles>
          ...
        <configuration>

Externalize the language strings to a property file.

The following message bundle is converted to a Java Class by Maven. In order to specify additional bundles just insert an additional “i18nMessagesBundle” tag in the pom.xml file.

Properties File

Create the directory that will hold the property file if not done so already:

mkdir -p src/main/resources/com/test/client

vi src/main/resources/com/test/client/Messages.properties

sendButton = Send
hello = Hello {0}!

Java code Change

Modify the java code to reference the property values instead of hard-coded strings.

--- matrix-blank/src/main/java/com/test/client/Matrix.java	
+++ matrix/src/main/java/com/test/client/Matrix.java	
@@ -10,10 +10,11 @@
 
 public class Matrix implements EntryPoint {
 
+	private final Messages messages = GWT.create(Messages.class);
 	public void onModuleLoad() {
-		Button button = new Button("Send", new ClickHandler() {
+		Button button = new Button(messages.sendButton(), new ClickHandler() {
 			public void onClick(ClickEvent event) {
-				Window.alert("Hello World!");
+				Window.alert(messages.hello("World"));
 			}
 		});
 		button.setStyleName("sendButton");

Test the Change

At this time you would want to verify that the Message.java class has been generated by the GWT framework. If this class is not found then try to mvn compile and refresh the eclipse project. You may also want to right click, update Maven project configuration if that does not work.

mvn compile gwt:run

The French Version

vi src/main/resources/com/test/client/Messages_fr.properties

sendButton = Envoyer
hello = bonjour {0}!

Configure

You can add the following line into the module element of your “gwt.xml” file for each locale you want to support:
vi src/main/java/com/test/Matrix.gwt.xml

<extend-property name="locale" values="fr"/>

Start the application

mvn compile gwt:run

Test the Change

At this point you have 2 options to see the French version of the site:

  1. You can put the following line in the host HTML page
    <meta name="gwt:property" content="locale=fr">
    
  2. Append the client property value to the query string of the URL: http://127.0.0.1:8888/Matrix.html?gwt.codesvr=127.0.0.1:9997?locale=fr

Either way you will see the French version of the site.

Reference

http://code.google.com/webtoolkit/doc/latest/tutorial/i18n.html

Appendix

Creating Keys for place holder parameters, the lines of the property file should look like this:

myString = First parm is {0}, second parm is {1}, third parm is {2}.

The key “myString” can be used in static HTML by wrapping the content with a html tag with an assigned id.

    <h1 id="myString"></h1>

The id attribute can be used as a handle to replace the text in Java code like this:

    RootPanel.get("appTitle").add(new Label(constants.myString("one","two","three")));

To get the list of locale’s supported by your JVM just run the following code:

	public static void main(String args[]) {
		Locale locales[] = SimpleDateFormat.getAvailableLocales();
		List arrayList = Arrays.asList(locales);
		Collections.sort(arrayList, new Comparator() {
			public int compare(Locale o1, Locale o2) {
				return o1.getDisplayName().compareTo(o2.getDisplayName());
			}
		});
		for (Locale locale : arrayList) {
			System.out.println(locale.toString() + "\t" + locale.getDisplayName());
		}
	}
18
Jun
12

Blank GWT Template Starter Application

This page describes the complete end-to-end process of creating and testing a blank “Hello World” type Google Web Tool kit (GWT) starter application using Maven. The page takes about 10-15 minutes to complete and have a working GWT application.

The application described on this page displays a Send Button on the page. It displays a JavaScript alert() message when the button is clicked.

Background

The GWT SDK allows you to generate an application using their generation tool. However I never liked using this tool because the application it generated is useless to me unless I understand how the application is working. The following page breaks down a simple Hello World GWT application step by step and allows the reader to follow along. Once the application is complete the user can import it into eclipse and use the GWT tool to modify the application using the Screen Design Tools.

Requirements

  • Maven
  • M2 Eclipse plugin
  • Eclipse GWT plugin

This page covers GWT version 2.4.0.

The first step is to create directory that will hold your project. The example below uses “matrix” as the project name. You can replace each instance of “matrix” with whatever you want to call your project.

mkdir matrix
cd matrix
# create some additional directories
mkdir -p src/main/java/com/test/client
mkdir -p src/main/webapp/WEB-INF

vi pom.xml

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

  <!-- POM file generated with GWT webAppCreator -->
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.test</groupId>
  <artifactId>matrix</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>GWT Maven Archetype</name>

  <properties>
    <!-- Convenience property to set the GWT version -->
    <gwtVersion>2.4.0</gwtVersion>
    <!-- GWT needs at least java 1.5 -->
    <webappDirectory>${project.build.directory}/${project.build.finalName}</webappDirectory>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>com.google.gwt</groupId>
      <artifactId>gwt-servlet</artifactId>
      <version>${gwtVersion}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>com.google.gwt</groupId>
      <artifactId>gwt-user</artifactId>
      <version>${gwtVersion}</version>
      <scope>provided</scope>
    </dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
</dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.7</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <!-- Generate compiled stuff in the folder used for developing mode -->
    <outputDirectory>${webappDirectory}/WEB-INF/classes</outputDirectory>
    <plugins>
      <!-- GWT Maven Plugin -->
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>gwt-maven-plugin</artifactId>
        <version>2.4.0</version>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>test</goal>
              <goal>generateAsync</goal>
            </goals>
          </execution>
        </executions>
        <!-- Plugin configuration. There are many available options, see
          gwt-maven-plugin documentation at codehaus.org -->
        <configuration>
          <runTarget>Matrix.html</runTarget>
          <hostedWebapp>${webappDirectory}</hostedWebapp>
        </configuration>
      </plugin>

      <!-- Copy static web files before executing gwt:run -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.1.1</version>
        <executions>
          <execution>
            <phase>compile</phase>
            <goals>
              <goal>exploded</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <webappDirectory>${webappDirectory}</webappDirectory>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Host HTML Page

The following is the Host HTML Page. The page imports the generated Javascript and starts the Javascript application. Similar to the “Entry Point” main() method of many other programming languages.

vi src/main/webapp/Matrix.html

<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <link type="text/css" rel="stylesheet" href="Matrix.css">
<script language="javascript" src="com.test.Matrix/com.test.Matrix.nocache.js"></script>
  </head>
  <body>
    <noscript>
      <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
        Your web browser must have JavaScript enabled
        in order for this application to display correctly.
      </div>
    </noscript>
    <div id="sendButtonContainer"></div>
  </body>
</html>

CSS Styles

GWT components are highly customizable. It makes sense to define sizes, colors, alighment, images, and other visual aspects of the component in CSS.

vi src/main/webapp/Matrix.css

.sendButton {
  display: block;
  font-size: 12pt;
}

Web Application Descriptor

The following is a basic web.xml file for the application.

vi src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <!-- Servlets -->
  <!-- Servlet-Mapping -->

  <!-- Default page to serve -->
  <welcome-file-list>
    <welcome-file>Matrix.html</welcome-file>
  </welcome-file-list>
</web-app>

vi src/main/java/com/test/Matrix.gwt.xml

<module>
  <inherits name='com.google.gwt.user.User' />
  <inherits name='com.google.gwt.user.theme.standard.Standard' />
  <entry-point class='com.test.client.Matrix' />
</module>

vi src/main/java/com/test/client/Matrix.java

package com.test.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.RootPanel;

public class Matrix implements EntryPoint {

	public void onModuleLoad() {
		Button button = new Button("Send", new ClickHandler() {
			public void onClick(ClickEvent event) {
				Window.alert("Hello World!");
			}
		});
		button.setStyleName("sendButton");
		RootPanel.get("sendButtonContainer").add(button);
	}
}

Run the project

mvn clean compile gwt:run

Click on Launch Default Browser button. You should see a page with a button on the top left.

Import into Eclipse and Edit in Design View

The following procedure allows you to open the screen above in the “GWT Design View”.

  1. Right click and import “Existing Maven Projects” into eclipse.
  2. Select the directory where the project is contained.
  3. Once the project is imported right click -> Properties -> Google -> Web Toolkit.
  4. Add the Entry point Module Matrix to the list if not already there by clicking on the “Add” button.
  5. Right click on the Matrix.java File and choose Open With -> WindowBuilder Editor.
  6. The source file will open and allow you to click on the Design Tab.
  7. Change the button name to Send2.

Test the change by typing:

mvn clean compile gwt:run

Might need to click on Launch Default Browser button twice. You should see a page with a button titled “Send2″ on the top left.

What’s Next?




Follow

Get every new post delivered to your Inbox.

Join 48 other followers