Wednesday, November 30, 2011

Cookies

HTTP is a 'stateless' protocol - each new HTTP request is completely independent of the previous requests.

Most web applications need to be 'stateful',for example, web applications that need to identify clients through the course of a session (login->to->logout).

They use cookies.

Cookies are name/value pairs which applications can associate with a response. The browser returns these cookies in future requests (until the cookie 'expires' at a preset date/time).

Cookies are the backbone of 'stateful' web applications.

Java Servlet API provides a Cookie API for setting cookies.

Let us set a cookie in our application that gives a unique ID to each user.
Here is how HelloWorld.java looks like with cookie:


 package org.confucius;   

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloWorld extends HttpServlet{
private static int nextUserId = 0;

public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String cookieValue = getCookieValue(request.getCookies(), "userId");

if (cookieValue == null){
Cookie userCookie = new Cookie("userId", String.valueOf(nextUserId));
response.addCookie(userCookie);
nextUserId++;
}

response.getWriter().write("User ID = " + cookieValue);
}

private String getCookieValue(Cookie[] cookies, String cookieName) {
if (cookies == null)
return null;

for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookieName.equals(cookie.getName()))
return (cookie.getValue());
}
return null;
}
}


Let us understand what we did.

We maintain a static counter to track the next User ID (a simple integer)

We get the cookie from the request - if one is not found, we assign one.

Update your web.xml to direct the /home URL to HelloWorld Servlet:

 <web-app>   
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>org.confucius.HelloWorld</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/home</url-pattern>
</servlet-mapping>
</web-app>



If you build and deploy HelloWorld.war, then point your browser to:
http://localhost:8080/HelloWorld/home

You will first see that the User ID = null, becuase it starts off with no cookie.
If you refresh your browser, it will set the User ID to 1.
If you keep refreshing, it will continue to be 1.
If you close and restart your browser, the user ID will go to 2.

Note that since we did not explicitly set an expiry for the cookie, the cookie dies when the browser is closed.

Using Log4J in Web Application

Create a log4j.properties in your /classes folder - because only /classes and /lib folders will be in Tomcat ClassPath, the convention is to put all .properties files in /classes

Here is what my log4j.properties looks like:
  log4j.rootLogger=DEBUG, RollFileAppender   

log4j.appender.RollFileAppender=org.apache.log4j.RollingFileAppender
log4j.appender.RollFileAppender.File=${catalina.home}/logs/HelloWorld.log
log4j.appender.RollFileAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.RollFileAppender.layout.ConversionPattern=%p %d %C %L %n %m %n


Remember to specify a log4j dependency in your ivy.xml:
 <ivy-module version="2.0">  
<info organisation="org.confucius" module="helloworld"/>
<dependencies>
<dependency org="javax.servlet" name="servlet-api" rev="2.5"/>
<dependency org="log4j" name="log4j" rev="1.2.16"/>
</dependencies>
</ivy-module>


Run the Ant:resolve target to download log4j.jar if necessary.

Remember to add log4j.jar to Eclipse->Project->Properties->JAVA Build Path->Libraries

Update your HelloWorld.java to use log4j:

  package org.confucius;   

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.Logger;

public class HelloWorld extends HttpServlet{
public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
PropertyConfigurator.configure("log4j.properties");
Logger logger = Logger.getLogger(HelloWorld.class);
logger.debug("Received age check request");

int year = Integer.parseInt(request.getParameter("year"));
if (year > 1995)
response.getWriter().println("You are underage!");
else
response.getWriter().println("You may enter!");
}
}


Now if you build and redeploy HelloWorld.war, and do an age check, you will see a HelloWorld.log file in your tomcat_home/logs folder.

AJAX

For this simple example, a client-side age check worked. For other scenarios, involving maybe a database look-up or running special analytics, we may need a server-side check.

We can do this with AJAX.

AJAX is a standardized Javascript API for sending HTTP requests to the server and handling the response. For the server, the HTTP request sent from AJAX looks no different from the one sent by a browser. Therefore any Servlet can handle a AJAX request.

Let us update out example to use AJAX to do server-side age check.

Update the HelloWorld.java Servlet to do an age check (see below):

  package org.confucius;   

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloWorld extends HttpServlet{
public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
int year = Integer.parseInt(request.getParameter("year"));
if (year > 1995)
response.getWriter().println("You are underage!");
else
response.getWriter().println("You may enter!");
}
}


Update web.xml to redirect age-check requests to HelloWorld Servlet:

 <web-app>   
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>org.confucius.HelloWorld</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/age-check</url-pattern>
</servlet-mapping>
</web-app>




Now update HelloWorld.jsp to use AJAX:

 <html>  
<head>
<script type="text/javascript">
function ageCheck()
{
year = document.getElementById("birthdate").value.substring(0,4);

// Create AJAX object
var xmlhttp;

if (window.XMLHttpRequest)
xmlhttp=new XMLHttpRequest(); // IE7+, Firefox, Chrome, Opera, Safari
else
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5

// Associate a method for AJAX response
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200) // Successful response
alert(xmlhttp.responseText);
}

// Send AJAX request
xmlhttp.open("GET","http://localhost:8080/HelloWorld/age-check?year="+year,true);
xmlhttp.send();
}
</script>
</head>
<body>
<form>
Enter your birthday (yyyy-mm-dd): <input type="text" id="birthdate"/> <button type="button" onclick="ageCheck()">Check</button>
</form>
</body>
</html>



Now if you rebuild and deploy HelloWorld.war - you will be able to age check with an AJAX call to the Servlet.

Note: Instead of copying the HelloWorld.war each time to the /webapps directory, you can use the Tomcat Manager to undeploy/redeploy.

To do this:
Go to http://localhost:8080, click on the Manager App - you can login using the admin you created earlier (see your tomcat_home/conf/tomcat-users.xml file)

Tuesday, November 22, 2011

JSP - Javascript

Using javascript in JSP is no different from using it in HTML.

Update your HelloWorld.jsp with the following:
 <html>  
      <head>  
           <script type="text/javascript">  
                function ageCheck()  
                     {  
                          year = parseInt(document.getElementById("birthdate").value.substring(0,4));  
                          if (year > 1995)  
                               alert("You are underage!");  
                          else  
                               alert ("You may enter!");  
                     }  
           </script>       
      </head>  
      <body>  
           Enter your birthday (yyyy-mm-dd): <input type="text" id="birthdate" onblur="ageCheck()"/>  
      </body>  
 </html>  


We have an edit field which takes a birthday, then calls a javascript function to check for underage.

Run Ant:dist to rebuild HelloWorld.war, then deploy it to Tomcat.
You may need to restart Tomcat after cleaning the previous HelloWorld.

Point your browser to http://localhost:8080/HelloWorld/jsp/HelloWorld.jsp

Enter your birthdate, then tab out of the edit field to trigger the javascript function.

(The javascript function is attached to the onblur event, so tabbing out of the edit field triggers it.)

Monday, November 21, 2011

JSP - Calling Java class

Reduce your HelloWorld.java class from a Servlet to a POJO.
(POJO = Plain Old Java Object - one which does not extend any other class nor implement any external interface)

Like this:
 package org.confucius;  
   
 public class HelloWorld{  
      public static String getGreeting ()  
      {  
           return "Hello World!";  
      }  
 }  
   


Call this from your HelloWorld.jsp:
 <html>  
      <head>  
           <%@ page import="org.confucius.HelloWorld" %>  
      </head>  
      <body>  
           <p><%= HelloWorld.getGreeting() %></p>  
      </body>  
 </html  


Note that <% .. %> tells JSP that this is Java code.
Between these enclosures, you can write any Java code, just like you would write inside a Foo.java source file.

Cleanup your web.xml - we no longer using HelloWorld as a servlet:
 <web-app>  
 </web-app>  
   


Run Ant:dist target, then deploy HelloWorld.war to Tomcat.

If you point your browser to:
http://localhost:8080/HelloWorld/jsp/HelloWorld.jsp

You will see "Hello World!" - but this time the greeting has come from the HelloWorld.java POJO.

JSP - HelloWorld

In your HelloWorld project, create a directory /web-content/jsp
In the /jsp directory, create a file HelloWorld.jsp

Write (or copy/paste) the following in this file:
 <html>  
      <head>  
      </head>  
      <body>  
           <p>Hello World!</p>  
      </body>  
 </html>  


As you can see, this JSP is no different from an HTML page.

Update your Ant:dist target in build.xml to include this JSP in the war:
 <project name="HelloWorld" xmlns:ivy="antlib:org.apache.ivy.ant" >  
     
   <target name="resolve" description="--> retrieve dependencies with ivy">  
     <ivy:retrieve />  
   </target>  
   
      <target name="init" depends="resolve">  
           <mkdir dir="classes"/>  
           <mkdir dir="target"/>  
      </target>  
   
      <target name="compile" depends="init">  
           <javac srcdir="." destdir="classes">  
                <classpath>  
                 <pathelement location="lib/servlet-api-2.5.jar"/>  
                </classpath>  
           </javac>  
      </target>  
   
      <target name="dist" depends="compile">  
           <war destfile="target/HelloWorld.war" webxml="web.xml">  
                 <classes dir="classes"/>  
                 <lib dir="lib"/>  
                 <fileset dir="web-content"/>  
           </war>  
      </target>  
 </project>  


Run the Ant:dist target to create a new HelloWorld.war

Deploy it to Tomcat (you may need to stop the server, delete the exploded HelloWorld.war then restart).

Point your broser to:
http://localhost:8080/HelloWorld/jsp/HelloWorld.jsp

You should see 'Hello World!' - this time rendered by the JSP.

Note that because the /jsp directory is outside the WEB-INF, it is a public directory - so we are able to directly access its contents (HelloWorld.jsp)

JSP

For our HelloWorld servlet, we could get away with a single call to the response writer:
 response.getWriter().println("Hello World!");  


However, this doesn't work so well if you wanted to output an entire HTML.
For example, to generate 'Hello, World!' message as an HTML page, you will have to do:
           response.getWriter().println("<HTML>");  
response.getWriter().println("<HEAD>");
response.getWriter().println("</HEAD>");
response.getWriter().println("<BODY>");
response.getWriter().println("Hello World!");
response.getWriter().println("</BODY>");
response.getWriter().println("</HTML>");



Clearly, as your HTML gets more complex, the Servlet will become unreadable.

JSP (Java Server Pages) was introduced to solve this problem.

JSP is a templating technology - JSP pages (*.jsp) look exactly like HTML pages, plus they can make calls to Java classes.

Deploying the war

Copy the HelloWorld.war to your Tomcat installation in the /webapps directory.

Tomcat will automatically detect it, explode it, then read the web.xml and will be ready to run it.

Now point your browser to:
http://localhost:8080/HelloWorld/home

And you should see 'Hello World!'

The /HelloWorld in the URL told Tomcat to go to the HelloWorld.war and the /home was resolved to the HelloWorld servlet because of the way we configured the web.xml

Congratulations - you just wrote your first Java web application ;)

'Exploding' the war

Often, you will come across references to 'exploded war'. All this means is that the war file has been unzipped (or unwared or unjared - its the same thing).

If you explode the war, you will see that it contains the WEB-INF directory.
Look inside it to see the web.xml, /classes and /lib

You can deploy your application as a war, or copy the exploded war.

On Tomcat, when you deploy it as a war, it will automatically be exploded when Tomcat loads it.

On some other servers, like JBoss or Weblogic, a war file will not be exploded.

Packing our web application into a war

As mentioned before, to deploy our web application, we need to pack it into a war file.

We do this with the Ant "war" task.

Update your Ant build.xml 'dist' target as shown below:

 <project name="HelloWorld" xmlns:ivy="antlib:org.apache.ivy.ant" >  

<target name="resolve" description="--> retrieve dependencies with ivy">
<ivy:retrieve />
</target>

<target name="init" depends="resolve">
<mkdir dir="classes"/>
<mkdir dir="target"/>
</target>

<target name="compile" depends="init">
<javac srcdir="." destdir="classes">
<classpath>
<pathelement location="lib/servlet-api-2.5.jar"/>
</classpath>
</javac>
</target>

<target name="dist" depends="compile">
<war destfile="target/HelloWorld.war" webxml="web.xml">
<classes dir="classes"/>
<lib dir="lib"/>
</war>
</target>
</project>


In the war task, we tell Ant to build a HeloWorld.war, and we tell it where to get the web.xml, the classes and the lib folders.

Ant will automatically create a WEB-INF directory from this information.

So now if you run the Ant:dist target, you will see a HelloWorld.war in your /target

web.xml

Create a file web.xml in your HelloWorld project root directory (i.e. in the same place as .classpath).

Write (or copy/paste) the following:

 <web-app>  
  <servlet>  
   <servlet-name>hello</servlet-name>  
   <servlet-class>org.confucius.HelloWorld</servlet-class>  
  </servlet>  
   
  <servlet-mapping>  
   <servlet-name>hello</servlet-name>  
   <url-pattern>/home</url-pattern>  
  </servlet-mapping>  
 </web-app>  
   


As mentioned before, web.xml is the master specification which tells the Web Server how to run our Application.

Let us understand what it says.

First it tells the Web Server to map the HelloWorld class (servlet) to the friendly name 'hello'

Then it tell the Web Server to redirect any request for the URL /home to the 'hello' servlet.

Servlets API

As mentioned earlier, it is the Java Servlet API which is the key to writing Java Web Applications.

Any class which extends the HTTPServlet abstract class can handle web requests.
Any class which extends HTTPServlet becomes a 'Servlet'.

So to make our HelloWorld class a Servlet, extend the HTTPServlet abstract class, as shown below:

 package org.confucius;  
   
 import java.io.IOException;  
 import javax.servlet.ServletException;  
 import javax.servlet.http.HttpServlet;  
 import javax.servlet.http.HttpServletRequest;  
 import javax.servlet.http.HttpServletResponse;  
   
   
 public class HelloWorld extends HttpServlet{  
      public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException  
      {  
           response.getWriter().println("Hello World!");  
      }  
 }  
   


Implementing the doGet method makes it possible for HelloWorld to receive HTTP requests.

Writing to the response will make the text appear on the browser.

In order for this class to compile, we need the servlet-api.jar

Update your ivy.xml file to get the servlet-api.jar, as shown below:

 <ivy-module version="2.0">  
   <info organisation="org.confucius" module="helloworld"/>  
   <dependencies>  
     <dependency org="javax.servlet" name="servlet-api" rev="2.5"/>  
   </dependencies>  
 </ivy-modu  


If you now run the ant:resolve task, it will download the servlet-api.jar to your /lib

For Eclipse to detect it, right click on HelloWorld project, the go to:
Properties->Java Build Path->Libraries->Add Jars

then add the /lib/servlet-api.jar

Now run the ant:compile task to compile HelloWorld.java

WEB-INF and CLASSPATH

Back again to Classpath.

When the web server runs your web application, it looks at the WEB-INF/classes and WEB-INF/lib to find classes. If you put your class or jar somewhere else, it will not find it.

One result of this is that you will often find *.properties files in WEB-INF/classes.

Just like .class files, Java classloaders also look for .properties files in the Classpath. So if your application needs a .properties file, it needs to be in the Classpath - which for a web application is the WEB-INF/classes or WEB-INF/lib

Maybe there should have been a WEB-INF/properties - but somebody didn't think of that, so we have to do this workaround of putting the properties files in /classes.

WEB-INF

A Java Web Application has certain specifications.

First, it should be packaged as a 'war' file. A 'war' file is a special case of a 'jar' file. It tells the web server that this file contains a web application.

Second, it should contain a folder called 'WEB-INF'.
WEB-INF itself should contain two folders: /classes and /lib, which contain all your Java classes and external libraries (jars)

WEB-INF should also contain a web.xml - this is the master file which tells the web server how to run your application.

Besides the WEB-INF, the web application can contain any number of other directories, for example /images, /jsp, /html, /js, etc

The difference between WEB-INF and all the other directories is that the web server will NEVER allow direct access to the contents of WEB-INF.

For example, if a user pointed their browser to:
http://www.foo.com/yourapp/WEB-INF/../..

they will get an error.

But if they point to:
http://www.foo.com/yourapp/some-other-folder/../..

they will get the file if it exists.

Contents of WEB-INF can only be accessed by classes and libraries inside WEB-INF.

In other words, WEB-INF is the private and mandatory directory in your web application. All other directories are public and optional.

Friday, November 18, 2011

Tomcat is a Java app

Note that Tomcat itself is written in Java.

If you look inside the /bin and /lib directories of your Tomcat installation, you will see jar files.

As is true of all Java programs, it is the JRE that is running Tomcat.

Just like how we saw earlier how Eclipse is a Java app running inside the JRE.

Tomcat - creating an admin account

Creating a admin account allows you to manage Tomcat from your browser.

The management options are usually available from the Welcome screen when you point your browser to:
http://localhost:8080/

Different versions of Tomcat can have different ways to create admin and user accounts
(they keep enhancing this)

For Tomcat 7.0, open the tomcat-users.xml file that you will find in your Tomcat deployment in the /conf directory.

Add to it an admin user with the 'manager-gui' role, as shown below:
(I have removed all the comments from the file, for clarity)
 <?xml version='1.0' encoding='cp1252'?>  
<tomcat-users>
<user username="admin" password="s3cret" roles="manager-gui"/>
</tomcat-users>



manager-gui is one of the Tomcat roles, which allows you to manage Tomcat from the browser. There are also other roles which you can read up in the Tomcat documentation.

You can create as many users as you want, and assign them any combination of roles.

Tomcat - Download / Install

Download the installer from Apache Tomcat's website and run it.
You can use all the defaults in the wizard.

It will create an entry in the Start->Programs for 'Monitor Tomcat'.
If you run this utility, a small icon will appear in your Start task bar tray (near the clock in the bottom right corner)

Right clicking on this monitor utility allows you to start/stop Tomcat.

Once Tomcat is started, point your browser to:
http://localhost:8080

You should see the Tomcat welcome screen.

Thursday, November 17, 2011

Understanding URLs - protocols, domains and ports

URLs are at the heart of the internet. So let us make sure we understand them clearly.

URLs are of the form:
protocol://domain:port/something/something/something/something/..

In browsers, protocol is almost always http.
But it can also be ftp, sftp, https, or your_custom_protocol.

Protocol tells the browser how to communicate with the server.

If you are communicating with a http server, you need to specify the protocol as http. Obviously, if you specify a different protocol, say ftp, you will get an error - because the browser will be talking greek to a french server.

Secondly, note that while the URL format does not stop you from specifying any protocol, even your_custom_protocol, it will work only if the browser knows how to communicate in that protocol. Most modern browsers can do http, ftp, sftp, https.

Domain is the DNS name of the server - like www.yahoo.com, www.google.com, www.apache.org

The browser first contacts the DNS server of your network service provider, to find out the IP address of the server that is registered for that domain. Once it knows the IP address, it can contact the server.

Port is very often not specified, and defaults to 80 for http (21 for ftp, 22 for sftp, 443 for https).

What exactly is port?
Port comes from TCP. Suppose you run two different network services on the same computer - say service1 and service2. When a network packet is received, how can TCP know which service to give the packet to? It uses a number called port. Port is just an integer and, in principle, can be anything >= 1. That said, several port numbers have been reserved and are called well-known ports - 80 for HTTP, 21 for FTP, etc.

When you start a TCP/IP service on a computer, you have to specify a port number.
When clients try to access this service, they need to specify the port number.

If you create your_custom_service, and you start it on the server at port 998, then your URL will have to specify 998.

If you start a HTTP server, but use a non-default port, say 212 - then you will need to specify 212 in your URL. Otherwise, by default the browser will use 80 - and it will give you an error that no HTTP service is running on port 80.

The last part of the URL /something/something/something - is of no importance to the browser. It doesn't care to understand it. It just passes it as a string to the web server. It is for the server to interpret it.

How does the server interpret it?

Depends on the server.

A simple HTTP server can interpret it as dir1/dir2/dir3/file
A Java web server can interpret it as context/servlet/args

We will learn more about this (something/something/something) part of the URL later.
For now, know that the browser does not interpret it at all.

Wednesday, November 16, 2011

Java-enabled Web Servers

For our application to be accessible from a browser, it has to be deployed on a Web Server.

Because we are writing our application in Java, it has to be deployed on a Java-enabled Web Server.

A Java-enabled Web Server is one that implements the so called 'Servlet API'.
Servlet API allows us to write Java Applications which can respond to HTTP requests from a browser.

The most popular Java-enabled Web Server is Apache Tomcat. Others are JBoss, BEA WebLogic, IBM WebSphere, Sun GlassFish, Jetty, Apache Geronimo, etc

Each has its pros and cons - we will try and play with as many of them as possible (some are commercial, so we can't play with them).

Creating a Web Application

Our HelloWorld application needs to be accessible from a browser - it has to be a web application.

For this we need to understand the difference between Desktop and Web Applications.

The main difference is that Desktop applications are 'multi-deploy' and 'single-user'.
Web Applications are 'single-deploy' and 'multi-user'.

Because a Desktop Application is tied to the computer on which it is installed, it enjoys implicit security. Only the user who owns the computer can use the application (assuming the computer is physically secured). Secondly, any data that needs to be persisted can be written to files on that computer. Thirdly, if the installation runs into issues, or there is a bug, only the single user is affected.

Web Applications need to be secured via logins and firewalls. Secondly, data needs to be persisted in a database, because with potentially millions of users using it simultaneously, reading and writing to files will not scale. Thirdly, any downtime or bug affects ALL users - for a business critical application, this is a catastrophe!

Inspite of this, web applications are very popular - how else can google and amazon reach out to millions of users?!

Web applications are lot more challenging to design. The good news is that a lot of solutions have already been found. We will learn about these solutions step by step.

Log4j - Configuration

So far we have been using log4j in its most basic configuration, which sent message to the console.

Let us now upgrade it to write to a log file, in addition to console.

To do this, create a log4j.properties in your HelloWorld project directory (i.e. in the same pace as .classpath).

Type or copy-paste the following:
 # Set root logger level to DEBUG and its only appender to Appender1 & Appender2.  
 log4j.rootLogger=DEBUG, Appender1,Appender2  
   
 # Appender1 is set to be a ConsoleAppender.  
 log4j.appender.Appender1=org.apache.log4j.ConsoleAppender  
 log4j.appender.Appender2=org.apache.log4j.RollingFileAppender  
 log4j.appender.Appender2.File=HelloWorld.log  
   
   
 # Appender1 uses PatternLayout.  
 log4j.appender.Appender1.layout=org.apache.log4j.PatternLayout  
 log4j.appender.Appender1.layout.ConversionPattern=%p %d %C %L %n %m %n
   
 log4j.appender.Appender2.layout=org.apache.log4j.PatternLayout  
 log4j.appender.Appender2.layout.ConversionPattern=%p %d %C %L %n %m %n


Let us understand this configuration.

We are configuring the root logger with DEBUG level - this means that all log messages will get logged.

We are attaching it to two appenders - one which writes to the console, and the other to a file. We want the log file to be named HelloWorld.log.

RollingFileAppender, by default, will limit the log file size to 10 MB. When that limit is reached, it will start a new log file. This is very important for web applications which run for months without restarts. It prevents log files from getting humongous and un-openable in editors. Note that the RollingFileAppender can be configured in various ways - the most popular one being to roll log files on a daily basis (instead of by size).


we are associating a pattern to the appenders to tell it how to format the messages.
%p --> debug level of the message
%d --> time stamp
%C --> class which generated the message
%L --> line of code which generated the message
%n --> newline
%m --> message to be logged

Update your HelloWorld.java code to load this configuration, as shown below:
 package org.confucius;  
   
 import org.apache.log4j.PropertyConfigurator;  
 import org.apache.log4j.Logger;  
   
 public class HelloWorld {  
      public static void main(String[] args) {  
     PropertyConfigurator.configure("log4j.properties");  
           Logger logger = Logger.getLogger(HelloWorld.class);            
           logger.debug("Hello World!");  
      }  
 }  
   


Now if you rebuild and run HelloWorld.jar, you will see a HelloWorld.log file created.

Tuesday, November 15, 2011

Thinking about Classpaths..

Let us take a moment to recollect how many different Classpaths we have specified so far:
- A Classpath as a environment variable when we ran javac from a command line
- A Classpath in build.xml which Ant used to build HelloWorld.jar
- A Classpath in MANIFEST.MF which java used to execute the HelloWorld.jar
- A Classpath in Eclipse so it knew how to compile HelloWorld.java
- A Classpath for Ant in Eclipse so it could load Ivy
- A Classpath for Eclipse Run Configuration so it could find HelloWorld.jar

As you can see, Classpath is a ghost that will always follow you in Java(!)
Because every Java program comes with its own ClassLoader which needs a Classpath.

Eclipse - Run Configuration

To run HelloWorld.jar in Eclipse, we will create a 'Run Configuration'

Go to Run->Run COnfigurations->Java Application->HelloWorld

Eclipse created this Run Configuration when we earlier ran the /bin/HelloWorld.class
Now we want to run /target/HelloWorld.jar

Go to the Classpath tab -> User Entries:
Remove -> 'HelloWorld(default classpath)'
Add jar -> /target/HelloWorld.jar

If you run this configuration, it will execute the HelloWorld.jar

Ant in Eclipse - Which JDK?

When we ran Ant from command line, it picked the JDK from the JAVA_HOME environment variable.

But when we run it in Eclipse, it picks the JDK provided by Eclipse (which is the JDK specified in the eclipse.ini file, with the -vm argument).

Ant in Eclipse - Which CLASSPATH? -

Remember that Eclipse uses the .classpath file to decide the CLASSPATH

Ant uses the CLASSPATH specified in the build.xml

If there is a mismatch, then it is possible that one of them will show an error while the other is happy.

Note also that in our current configuration, Eclipse is compiling to the /bin while Ant is compiling to the /classes

Eclipse - Ant & Ivy

Eclipse is Ant ready out of the box.

To make it Ivy ready, go to Window->Preferences->Ant->Runtime->Classpath->Global Entries

Click on 'Add External Jars' and add the Ivy jar that we downloaded earlier.
Now it is Ivy ready.

Copy the build.xml, ivy.xml, ivysettings.xml and ivysettings-public.xml to the HelloWorld project folder (in the root directory, same place as .classpath)

Go to Window->Show View->Ant - this will open the Ant viewer

Drag drop the build.xml to this viewer - you will now be able to see all the Ant targets in the build.xml

Double-click on the 'dist' target to build the HelloWorld.jar

In the Navigator view, select the HelloWorld project and hit F5 to refresh.

You will now see the /target and /classes directories that were added by the Ant build.

If you delete the /lib directory, then rerun Ant 'dist' and refresh - you will see the /lib reappear with the log4j.jar - proof that Ivy is working.

Eclipse constantly compiles code

Remember that Eclipse compiles code constantly as you type!
It uses the .classpath to locate classes and jars.

That is how it is able to red mark problems and also show you helpful popups (like a list of public methods in a class).

Eclipse - HelloWorld

Right click on your HelloWorld project and select New Class

In the wizard, enter the package name 'org.confucius' and the class name 'HelloWorld'

A new file HelloWorld.java will be created in the /src/org/confucius folder

Enter the following code - it is the same code which we wrote earlier.
(Notice that this time we have a package org.confucius for the class)

 package org.confucius;  
   
 import org.apache.log4j.BasicConfigurator;  
 import org.apache.log4j.Logger;  
   
   
 public class HelloWorld {  
      public static void main(String[] args) {  
           // Set up a simple configuration that logs on the console.  
           BasicConfigurator.configure();  
           Logger logger = Logger.getLogger(HelloWorld.class);  
           logger.debug("Hello World!");  
      }  
 }  
   


Eclipse will red mark all references to the log4j classes because we do not have log4j.jar in the CLASSPATH

Let us add log4j.jar

Right Click on the HelloWorld project directory, and select New->Folder

Name the folder 'lib'

Copy the log4j.jar to this folder


Right Click on the HelloWorld project directory, and select Properties->Java Build Path->Libraries

Click on 'Add Jars' and select the /lib/log4j.jar

The red marks should all disappear and you will see the HelloWorld.class created in /bin/org/confucius

Right Click on the HelloWorld project directory, and select Run As->Java Application-->HelloWorld

You will see the output in the Eclipse Console.

Note: Open the .classpath and notice the entry for log4j.jar

Monday, November 14, 2011

Eclipse - .project and .classpath files

Two very important files in your Eclipse project are the .project and .classpath

Both of these are XML files.

.project contains all the information about your project and Eclipse reads this when it opens your project

.classpath contains the CLASSPATH of the project and Eclipse reads this when it compiles your project

Tip: If you give your Eclipse project to your colleagues, you will need to give them both these files. If your .classpath contains absolute paths that are specific to your machine, it can cause problems to other members of your team. This is a common problem when people checkin an Eclipse project into a source control system for team sharing.

Eclipse - Package Explorer and Navigator

Eclipse lets you see your project files in different 'views'.
You can open any View from Windows-->Show View

Two useful Vies are 'Package explorer' and "Navigator'

Package Explorer shows you all the java classes and libraries (jars) in your project - organized by the package each class belongs to.

Navigator is exactly like Windows Explorer - it shows you all the files in your project as appear in your file system.

We recommend you open both these views.

Eclipse - HelloWorld project

If you haven't already done so, open the eclipse.ini file in your eclipse installation, and specify the -vm argument:
 -vm   
 C:\Program Files\Java\jdk1.6.0_21\bin\javaw.exe  
   



Create a workspace in your home directory called workspace-confucius.
For example, I have:
C:\Documents and Settings\LavanniM\workspace-confucius

Start Eclipse by double clicking on the eclipse.exe in your eclipse installation.

When it asks you for the workspace, specify the workspace-confucius folder.

When Eclipse comes up, it will show you the Welcome screen - you can close it, and then you will see the regular Eclipse editors.

Take a moment to look into the /workspace-confucius directory.

You will see that Eclipse has created the .metadata directory for this workspace.

In Eclipse, go to File->New->Project.. and select the Java-->Java Project

Name the project HelloWorld and use all the defaults in the Wizard

When the process completes, you will see the HelloWorld project in your Package Explorer window.

Eclipse - Workspace

Eclipse has this fundamental notion of 'workspace'.

A workspace is just a folder on your file system - something as simple as:
C:\users\joe\workspace

Eclipse uses this folder to keep all its projects. So for each Eclipse project, you will see a corresponding directory in the /workspace

In addition, you will see a directory called /.metadata - this is the place where Eclipse will keep all its workspace-wide settings.

For example, it will remember how you like your various editors configured, how you want to setup your run time configuration, how you want to connect to CVS or Subversion, which projects you have opened, etc

You can have multiple workspaces, for example:
C:\users\joe\workspace-my-projects
C:\users\joe\workspace-company-projects
C:\users\joe\workspace-experimental-projects

If you frequently switch between different versions of Eclipse, you might even have something like:
C:\users\joe\workspace\europa
C:\users\joe\workspace\helios
C:\users\joe\workspace\indigo

Everytime you open Eclipse, it will ask you which workspace you want to use, then load the settings and projects from that workspace.

Tip - Sometimes, due to strange reasons, your workspace can get corrupted and Eclipse will start throwing weird errors. Deleting the .metadata folder usually resolves these issues (but you have to redo all your workspace settings)

java.exe vs javaw.exe

Inside your JRE /bin directory, you will find java.exe and javaw.exe - whats the difference?

java.exe and javaw.exe are the exact same thing, with one tiny difference. When you run java.exe, it opens in a console. On the other hand, javaw.exe does not pop up any console.

This is good when an external program, like eclipse.exe or your browser, runs JRE - so you do not see annoying console windows popping up.

(javaw >>> java "windowless")

Eclipse - the eclipse.ini file

If you understand this post, you will save yourself a lot of trouble later.

Eclipse is a java program - just a jar. So it needs to run inside a JRE.

The eclipse.exe is a misnomer - it is not the eclipse program, but a program which starts the eclipse program. It does a few initialization things, then calls the eclipse jar with appropriate arguments (I am only showing the most important -jar argument here):

 > java -jar eclipse/plugins/org.eclipse.equinox.launcher_1.0.0.v20070606.jar ..  
   

We are familiar with the above command. We are calling the JRE and giving it a -jar argument, pointing to the actual jar which runs Eclipse.

You could have as well called this command from the command line yourself, instead of running eclipse.exe (the misnomer).

Why is all this so important?

Because the immediate question to ask yourself is which JRE is Eclipse running under?

If you just think of the above command:
 > java -jar eclipse/plugins/org.eclipse.equinox.launcher_1.0.0.v20070606.jar ..  
   

we can be sure that the OS will look for the 'java' program in its PATH, and execute the first JRE it finds.

This is problematic.

Why?

Because what if we have multiple installs of JDK on our machine (this is quite common) - JDK5, JDK6 and JDK7?

If we are not absolutely sure what is in our PATH, we cannot be sure what Eclipse is running in. Consequently, when we compile our java source in Eclipse, we do not know for sure if we compiled in JDK5, JDK6 or JDK7!

This can lead to serious compilation issues. Sometimes it will give compilation errors because it can't find the necessary libraries, other times it will compile against an older version of the libraries, etc, etc.

To avoid going down this labyrinth of problems, we have the eclipse.ini file.

Instead of relying on the OS to figure out what JRE to use, we will specify it upfront - so we can be absolutely sure what version of JDK we are using.

Open the eclipse.ini file in a text editor - you will find this file in the top directory of your eclipse installation.

At the very top, add the following two lines:
 -vm   
 C:\Program Files\Java\jdk1.6.0_21\bin\javaw.exe  
   

This tell eclipse which Virtual Machine to use.

Note that we are specifying the javaw.exe, and not java.exe - what is the difference?

java.exe and javaw.exe are the exact same thing, with one tiny difference. When you run java.exe, it opens in a console. On the other hand, javaw.exe does not pop up any console.

This is good when an external program, like eclipse.exe or your browser, is runs JRE - so you do not see annoying console windows popping up.

Eclipse - Download

Download eclipse from their website - it downloads as a zip file.
Unzip it to an appropriate directory (say, C:\Program Files\Eclipse)

Note that Eclipse versions have alphabetical names - europa, galileo, helios, indigo.

If you want to have multiple versions of Eclipse, you might want to install them in different folders:
C:\Program Files\Eclipse\europa
C:\Program Files\Eclipse\galileo
C:\Program Files\Eclipse\helios
C:\Program Files\Eclipse\indigo

That way you can use any version of Eclipse as necessary.

You will see an eclipse.exe in the top folder. You can double click on this to start Eclipse, or copy it to your Desktop or Start Bar as a shortcut.

To start Eclipse from command line, add the Eclipse directory to your PATH environment attribute.

Friday, November 11, 2011

Eclipse

For real Java projects, you need more that a text editor to work efficiently.
You need an IDE (Integrated Development Environment) which can manage your source files, help you code faster, check your code as you type, manage your deployment, testing, etc,

Enter Eclipse - the free IDE.

Eclipse can do a lot of things out of the box. Additionally, people have written all kinds of plugins which make it do even more.

FYI - Eclipse releases are usually named in alphabetical order - Callista, Europa, Ganymede, Galileo, Helios, Indigo.

Ivy - Custom Configuration

In addition to the Maven Central Repository, let us say we want Ivy to also look up the JBoss public repository at https://repository.jboss.org/nexus/content/repositories/

The best way to configure Ivy is to start with the default configuration and incrementally introduce our changes.

Create a ivysettings.xml in your /confucius directory (i.e. the same directory where you have HelloWorld.java) and make it match the default ivysettings.xml.

Like this:
 <ivysettings>  
  <settings defaultResolver="default"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-public.xml"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-shared.xml"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-local.xml"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-main-chain.xml"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-default-chain.xml"/>  
 </ivysettings>  
   


Next, we will override the default ivysettings-public.xml

We will create our own ivysettings-public.xml in the /confucius directory and point the ivysettings.xml to it.

So your updated ivysettings.xml will look like this:

 <ivysettings>  
  <settings defaultResolver="default"/>  
  <include url="./ivysettings-public.xml"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-shared.xml"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-local.xml"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-main-chain.xml"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-default-chain.xml"/>  
 </ivysettings>  
   


Now create a ivysettings-public.xml in your /confucius directory and type (or copy-paste) the following in it:

 <ivysettings>  
     <resolvers>  
                 <url name="public">  
                      <artifact pattern="http://repo1.maven.org/maven2/[organization]/[module]/[revision]/[artifact]-[revision].[ext]" />  
                      <artifact pattern="https://repository.jboss.org/nexus/content/repositories/[organization]/[module]/[revision]/[artifact]-[revision].[ext]" />  
                </url>  
     </resolvers>  
 </ivysettings>  
   
   
   


This tells Ivy to use both the Maven central repository and the JBoss public repository when looking for jars.

Note that we have used the 'Url' Resolver - this Resolver looks for jars in any repository accessible with urls.

Ivy - Default configuration

Ivy is configured in a ivysettings.xml file.

Now the interesting news - this ivysettings.xml can point to other settings.xml files, which in turn can point to other settings.xml and so on.

So, you can restrict ivy configuration to a single long ivysettings.xml file or distribute it to several short (specific) settings.xml files.

By default, Ivy ships with a ivysettings.xml file which points to 5 other settings.xml file.

It looks like this:

 <ivysettings>  
  <settings defaultResolver="default"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-public.xml"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-shared.xml"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-local.xml"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-main-chain.xml"/>  
  <include url="${ivy.default.settings.dir}/ivysettings-default-chain.xml"/>  
 </ivysettings>  
   


The ${ivy.default.settings.dir} by default points to the ./ivy directory in your home directory

The ivysettings-public.xml, ivysettings-shared.xml, ivysettings-local.xml files point to resolvers for your public, shared and local repositories.

The ivysettings-main-chain.xml and ivysettings-default-chain.xml specify the order in which Ivy will look at repositories.

Ivy - More about Repositories

Ivy looks for jars in not one, but three repositories - local, shared and public.

Local is your private repository - if you want Ivy to pick a certain specific jar no matter what else happens in the world outside, this is the place to put it.

Shared is a repository shared with your team. If there are certain jars that all your team members must use, this is the place to keep them.

Public is a repository that you do not expect to have control over - like the central Maven Repository.

By default, Ivy will looks for a jar first in the local, then in the shared and finally in the public repository.

Thursday, November 10, 2011

Ivy - Resolvers

Just like JRE uses ClassLoaders to find and load classes, Ivy uses Resolvers to find and download jars.

Ivy comes with standard Resolvers. But anyone can write custom Resolvers if they want to.

Each Resolver has its won specific way of configuration, which tells it where to download the jars from.

By default, Ivy will use its standard Resolvers configured in a standard way.

If necessary, you can override the standard settings to tell Ivy to use a Resolver of your choice, configured your way.

In an upcoming post, we will use a Resolver to  direct Ivy to use a non-default repository.

Ivy in Ant

You have to do 3 things.

One, create an xmlns:ivy name space in your build.xml
Two, create a ivy:retrieve target
Three, create a file ivy.xml and define your dependencies

See this HelloWorld build.xml updated to use Ivy - note that we have made the 'init' target dependent on the 'resolve' target:

 <project name="HelloWorld" xmlns:ivy="antlib:org.apache.ivy.ant" >  
     
   <target name="resolve" description="--> retrieve dependencies with ivy">  
     <ivy:retrieve />  
   </target>  
   
      <target name="init" depends="resolve">  
           <mkdir dir="classes"/>  
           <mkdir dir="target"/>  
      </target>  
   
      <target name="compile" depends="init">  
           <javac srcdir="." destdir="classes">  
                <classpath>  
                 <pathelement location="lib/log4j-1.2.16.jar"/>  
                </classpath>  
           </javac>  
      </target>  
   
      <target name="dist" depends="compile">  
           <jar jarfile="target/HelloWorld.jar" basedir="classes">  
                <manifest>  
                     <attribute name="Main-Class" value="HelloWorld"/>  
                     <attribute name="Class-Path" value="HelloWorld.jar ../lib/log4j-1.2.16.jar"/>  
                </manifest>  
           </jar>  
      </target>  
 </project>  



Here is the ivy.xml to download the log4j.jar - notice how the log4j co-ordinates are specified using the org, name and rev attributes.

 <ivy-module version="2.0">  
   <info organisation="org.confucius" module="helloworld"/>  
   <dependencies>  
     <dependency org="log4j" name="log4j" rev="1.2.16"/>  
   </dependencies>  
 </ivy-module>  


Now if you run Ant, it will call Ivy to do the log4j download (see below)

 C:\users\LavanniM\confucius>ant dist  
 Buildfile: C:\users\LavanniM\confucius\build.xml  
   
 resolve:  
 [ivy:retrieve] :: Ivy 2.2.0 - 20100923230623 :: http://ant.apache.org/ivy/ ::  
 [ivy:retrieve] :: loading settings :: file = C:\users\LavanniM\confucius\ivysettings.xml  
 [ivy:retrieve] :: resolving dependencies :: org.confucius#helloworld;working@LavanniM-01  
 [ivy:retrieve] confs: [default]  
 [ivy:retrieve] found log4j#log4j;1.2.16 in default  
 [ivy:retrieve] :: resolution report :: resolve 93ms :: artifacts dl 0ms  
     ---------------------------------------------------------------------  
     |         |      modules      ||  artifacts  |  
     |    conf    | number| search|dwnlded|evicted|| number|dwnlded|  
     ---------------------------------------------------------------------  
     |   default   |  1  |  0  |  0  |  0  ||  1  |  0  |  
     ---------------------------------------------------------------------  
 [ivy:retrieve] :: retrieving :: org.confucius#helloworld  
 [ivy:retrieve] confs: [default]  
 [ivy:retrieve] 1 artifacts copied, 0 already retrieved (470kB/15ms)  
   
 init:  
   
 compile:  
   [javac] C:\users\LavanniM\confucius\build.xml:13: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds  
   
 dist:  
   
 BUILD SUCCESSFUL  
 Total time: 0 seconds  
   
 C:\users\LavanniM\confucius>  

Ivy - Download / Install

Download Ivy from the Apache Ivy website, unpack the downloaded zip file wherever you want, and copy the ivy jar file into your Ant /lib directory.

Ivy - Repository 'co-ordinates'

If Ivy just told the repository server give me john.jar, the repository server will ask, "The one from John Lennon or John McEnroe? And you want version 1.0 or 2.6.2?"

This is called co-ordinates.

Each company/organization/individual that uploads jars to the repository is assigned a unique 'address space'. For example, log4j has 'log4j' (what a surprise), apache has 'org.apache', hibernate has 'org.hibernate', etc

Each company can then create its own address space inside for its different jars.
For example, Spring has the address space org.springframework, under which it has spring-aop, spring-core, spring-jmx, spring-jca, etc

So if Ivy wants the version 2.3 of spring-jmx.jar, it will tell the repository server:
organization = org.springframework
jar name = spring-jmx
revision = 2.3

These 3 specifications are called the co-ordinates of the jar.

Ivy

We have used the log4j.jar in our project.We downloaded this jar manually.

A typical Java project can use hundreds of external jars. To expect to download these manually, tracking version numbers and all that can quickly become a harrowing experience.

Enter Ivy.

Ivy is a Ant extension - tell Ivy what jars you want, and it will do the downloading for you.

That sounds simple enough. But how does Ivy know where to download the jar from?

From a place called 'repositories'.

A repository is an online directory (location is http://repo1.maven.org/maven2) which contains ALL jars from all over the world.

Whenever Mr. John Whoever writes a new library john.jar, he uploads it to the repository. When you tell Ivy you need john.jar, Ivy downloads it from the repository.

If this sounds too easy, and you are not yet confused - let us learn some more things about repository.

After Ivy downloads the john.jar, it keeps a local copy of it in a local directory - by default in your home directory/.ivy/

This way, Ivy does not have to download from the online (central) repository each time - this makes builds go faster.

Second, http://repo1.maven.org/maven2 is a central repository really maintained by the 'Maven Project' - Maven is the competitor of Ant :)

Third, not every John likes to save his jar to Maven central repository - so there are other online repositories (jBoss has its own, SpringSource as well). So every once in a while you have to point Ivy to a different repository.

Wednesday, November 9, 2011

Ant - Run

To run Ant and build the HelloWorld.jar, give the following command:
> ant dist

This tells Ant to run the 'dist' target.
Because 'dist' depends on 'compile' which depends on 'init', Ant will automatically run all three in the order init, compile, dist.

You can then run HelloWorld from the target directory (see below):

 C:\users\LavanniM\confucius>ant dist  
 Buildfile: C:\users\LavanniM\confucius\build.xml  
   
 init:  
   
 compile:  
   [javac] C:\users\LavanniM\confucius\build.xml:8: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds  
   
 dist:  
    [jar] Building jar: C:\users\LavanniM\confucius\target\HelloWorld.jar  
   
 BUILD SUCCESSFUL  
 Total time: 0 seconds  
   
 C:\users\LavanniM\confucius>cd target  
   
 C:\users\LavanniM\confucius\target>java -jar HelloWorld.jar  
 0 [main] DEBUG HelloWorld - Hello World!  
   
 C:\users\LavanniM\confucius\target>  


Note - you can ignore the includeantruntime warning if you get it - its a known and harmless bug in Ant 1.8

Ant - build.xml

When you run Ant, it looks for a file called build.xml, then follows the instructions in it.

Create a new file called build.xml in /confucius - i.e., in the same directory where you have the HelloWorld.java

Type in (or copy-paste) the following in build.xml - these are the instructions for building HelloWorld.jar

 <project name="HelloWorld">  
      <target name="init">  
           <mkdir dir="classes"/>  
           <mkdir dir="target"/>  
      </target>  
   
      <target name="compile" depends="init">  
           <javac srcdir="." destdir="classes">  
                <classpath>  
                 <pathelement location="lib/log4j-1.2.16.jar"/>  
                </classpath>  
           </javac>  
      </target>  
   
      <target name="dist" depends="compile">  
           <jar jarfile="target/HelloWorld.jar" basedir="classes">  
                <manifest>  
                     <attribute name="Main-Class" value="HelloWorld"/>  
                     <attribute name="Class-Path" value="HelloWorld.jar ../lib/log4j-1.2.16.jar"/>  
                </manifest>  
           </jar>  
      </target>  
 </project>  



Let us try and understand what we have written.

We created an Ant project called "HelloWorld".

We created three Ant 'targets' - init, compile and dist (short for distribution).
'dist' depends on 'compile' which depends on 'init' - this means that if you run 'dist', it will first run 'compile' which itself will first run 'init'.

In the 'init' we created two directories.
Note that if these directories already exist, Ant will not create them.

In the 'compile', we told Ant to compile everything in the current direcrtory and place the .class files in the 'classes' directory.
We also specified a classpath so it can find the log4j.jar

In the 'dist' we told Ant to jar all the .class files in /classes directory and place the jar in /target.
We also told it to add some things to the MANIFEST.MF

Ant - Configuration

Add the /bin directory of Ant to your PATH and you should be good to go.
For example, my bin is here: C:\Program Files\ant\apache-ant-1.8.1\bin

Next, create a JAVA_HOME environment variable and set it to the location of your JDK.
For example, my JAVA_HOME looks like this:

JAVA_HOME=C:\Program Files\Java\jdk1.6.0_21

This tells Ant where to find Java compiler.

Ant - Download

Download the latest Ant from the Apache Ant website. It is distributed as a zip - just unzip it to an appropriate folder (say C:\Program Files\Ant)

At this time, you just need Ant, not Ivy - which is the dependency management plugin. We will come to that later.

Quick note about Java versions

When you download JRE or JDK, you see versions like jdk1.5.0_22, jdk1.6.0_21, etc
When you read doc, you see things like Java5, Java6, etc

What gives?

The idea behind Java versioning was x.y.z - where x is the major release number. The idea was that any change in x implied a very significant change - something that will not be backward compatible, a paradigm change, a point of serious transition.

A change in x would cause so many problems to so many people that at that point they will have to rename Java to Mocha!

So in real life, x always is 1 - so people have resorted to referring to Java version by y - hence Java5, Java6, etc

Tuesday, November 8, 2011

Ant

So far we have been giving commands like javac, jar from command line to build our program. Clearly this won't scale for a typical project that has hundreds of files, external libraries, etc.

We need a build tool - Enter Ant.

With Ant, we can do a declarative build. In a file called build.xml, we can specify what needs to be built and how - then Ant can do that. It makes things easier to manage.

ClassLoader (and CL Hell)

When we ran HelloWorld.class, we saw that the JRE looked at the CLASSPATH environment variable to know where to load the classes.

Then, when we ran HelloWorld.jar, we saw that the JRE looked at the Class-Path property in MANIFEST.MF

So what is going on? Why does the scenery change one way and another?!

Truth is that this scenery can keep changing in every environment!!
The culprit is called ClassLoader.

A ClassLoader is a special class which implements the ClassLoader abstract class.
You can write any number of ClassLoaders - MyFooClassLoader, MyYellowClassLoader, MyCrazyClassLoader, ...

JRE will call the ClassLoader to get a class it needs. Needless to say different ClassLoaders look in different places!!!

This leads to hair-tearing situations where Java programs deployed in one place work perfectly, and then stop working in another. Worse things can happen. If you happen to have two different versions of a class on your computer, one ClassLoader might pick one and another ClassLoader will pick the other. It will happen silently and your program can behave differently!!

This is ClassLoader HELL - it is an unsolved problem.

Running a jar - how confusing this can be!

Let us try running the HelloWorld program as a jar - just to see how confusing this can ge.

Lets first pack our HelloWorld.class in a HelloWorld.jar
Go to the directory which contains the HelloWorld.class and give the following command
> jar cf HelloWorld.jar HelloWorld.class

The 'c' stands for create and 'f' stands for send the output to file, in this case, HelloWorld.jar

Next, try to run the jar using the following command - it will fail (see below):
> java -jar HelloWorld.jar

 C:\users\LavanniM\confucius>jar cf HelloWorld.jar HelloWorld.class  
   
 C:\users\LavanniM\confucius>java -jar HelloWorld.jar  
 Failed to load Main-Class manifest attribute from  
 HelloWorld.jar  
   
 C:\users\LavanniM\confucius>  


What happened?

When we initially ran the HelloWorld program from HelloWorld.class,the JRE 'knew' it had to look for the 'main' method in the HelloWorld.class
But when faced with a jar, which can potentially contain N classes, each of which might have a 'main', it cannot figure out which class's 'main' method to call.

Enter MANIFEST.MF

Inside each jar, is a directory called META-INF, and inside the META-INF is a file called MANIFEST.MF

This is a simple text file with name:value property pairs

Here is what it's contents look like:
 Manifest-Version: 1.0  
 Created-By: 1.6.0_21 (Sun Microsystems Inc.)  
   
   


JRE looks into this file for a property called Main-Class - if it does not find it, it throws the error we saw above.

So how do we put a MAIN-CLASS property in MANIFEST.MF? We can't simply edit this file because it will get recreated with the jar, and our changes will be lost!

Here is how to do this:

Create a new file called Manifest.txt in /confucius
Add a key-value property to it:

 Main-Class: HelloWorld  
   


Very Important: The line has to end with a newline - so make sure you hit enter!

Now recreate the HelloWorld.jar with the following command:
> jar cfm HelloWorld.jar Manifest.txt HelloWorld.class

The 'm' stands for include name-value pairs in MANIFEST.MF

Now try to run the new HelloWorld.jar - it will fail again (See below)!

 C:\users\LavanniM\confucius>jar cfm HelloWorld.jar Manifest.txt HelloWorld.class  
   
 C:\users\LavanniM\confucius>java -jar HelloWorld.jar  
 Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/BasicConfigurator  
     at HelloWorld.main(HelloWorld.java:7)  
 Caused by: java.lang.ClassNotFoundException: org.apache.log4j.BasicConfigurator  
     at java.net.URLClassLoader$1.run(Unknown Source)  
     at java.security.AccessController.doPrivileged(Native Method)  
     at java.net.URLClassLoader.findClass(Unknown Source)  
     at java.lang.ClassLoader.loadClass(Unknown Source)  
     at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)  
     at java.lang.ClassLoader.loadClass(Unknown Source)  
     ... 1 more  
   
 C:\users\LavanniM\confucius>  


What now? Why can't it find the log4j class?

Turns out, when you run jar, JRE does not look at the CLASSPATH environment variable. It looks for a Class-Path property in the MANIFEST-MF.

So let us add another property, Class-Path, to our Manifest.txt - like this:

 Main-Class: HelloWorld  
 Class-Path: C:\users\LavanniM\confucius\HelloWorld.jar C:\users\LavanniM\confucius\lib\log4j-1.2.16.jar  
   


Again, do not forget to hit Enter (to add newline) after the last line.
Also, note that we use space to separate the two paths, and there are no semi-colons.

Try recreating the jar and running it - it will fail again (see below):

 C:\users\LavanniM\confucius>jar cfm HelloWorld.jar Manifest.txt HelloWorld.class  
   
 C:\users\LavanniM\confucius>java -jar HelloWorld.jar  
 Exception in thread "main" java.lang.NoClassDefFoundError: HelloWorld  
 Caused by: java.lang.ClassNotFoundException: HelloWorld  
     at java.net.URLClassLoader$1.run(Unknown Source)  
     at java.security.AccessController.doPrivileged(Native Method)  
     at java.net.URLClassLoader.findClass(Unknown Source)  
     at java.lang.ClassLoader.loadClass(Unknown Source)  
     at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)  
     at java.lang.ClassLoader.loadClass(Unknown Source)  
 Could not find the main class: HelloWorld. Program will exit.  
   
 C:\users\LavanniM\confucius>  


This time the reason is that we gave absolute path for the Class-Path property, but JRE expects relative paths.

Update the Manifest.txt as follows:

 Main-Class: HelloWorld  
 Class-Path: HelloWorld.jar lib\log4j-1.2.16.jar  
   



Now if you recreate the jar and run it, it will run (phew!)

 C:\users\LavanniM\confucius>jar cfm HelloWorld.jar Manifest.txt HelloWorld.class  
   
 C:\users\LavanniM\confucius>java -jar HelloWorld.jar  
 0 [main] DEBUG HelloWorld - Hello World!  

JVM

JVM - short for Java Virtual Machine.
Its the biggest buzzword in Java - and also its raison-de-etre.

When you create an executable for a C/C++ program, you have to target the OS - you have to have a different executable for Windows/Linux/Mac

Java source compiles into byte-code - which is always targeted for ONE virtual OS - aka JVM. It is the virtual environment in which all Java programs run - they are unaware of the actual OS they are running on. JVM is the abstract container which shields Java programs from underlying OS.

When you start the JRE (by running java.exe), it first creates a JVM - then runs your Java program in this JVM. If you have 5 (or n) java.exe processes running, you have 5 (or n) JVMs running.

Each Java program executes inside its own JVM.

Configuring CLASSPATH for jar libraries

When you include a directory in CLASSPATH, all the .class files become available to the JRE. But... not the .jar libraries.

You have to explicitly include the jar libraries in your CLASSPATH

For example, let us use log4j in our HelloWorld - instead of printing to console ourselves, we will use log4j to do it.

* Create a /lib directory under /confucius
For example, I have:
C:\users\LavanniM\confucius\lib

* Download the log4.jar (it will be something like log4j-1.2.16.jar - with the version) from the log4j website
* Copy the jar to the \lib

Now update your HelloWorld class to use log4j

 import org.apache.log4j.Logger;  
import org.apache.log4j.BasicConfigurator;

class HelloWorld {
public static void main(String[] args) {
// Set up a simple configuration that logs on the console.
BasicConfigurator.configure();
Logger logger = Logger.getLogger(HelloWorld.class);
logger.debug("Hello World!");
}
}




* Try compiling the source code (you will get a compilation error - see below):
 C:\users\LavanniM\confucius>javac HelloWorld.java  
HelloWorld.java:1: package org.apache.log4j does not exist
import org.apache.log4j.Logger;
^
HelloWorld.java:2: package org.apache.log4j does not exist
import org.apache.log4j.BasicConfigurator;
^
HelloWorld.java:7: cannot find symbol
symbol : variable BasicConfigurator
location: class HelloWorld
BasicConfigurator.configure();
^
HelloWorld.java:8: cannot find symbol
symbol : class Logger
location: class HelloWorld
Logger logger = Logger.getLogger(HelloWorld.class);
^
HelloWorld.java:8: cannot find symbol
symbol : variable Logger
location: class HelloWorld
Logger logger = Logger.getLogger(HelloWorld.class);
^
5 errors

C:\users\LavanniM\confucius>


Easy to see why - our log4j.jar is not in the CLASSPATH.

So try adding the /lib directory to the CLASSPATH and try again - it will still fail (see below)

 C:\users\LavanniM\confucius>set CLASSPATH=C:\users\LavanniM\confucius;C:\users\LavanniM\confucius\lib;  

C:\users\LavanniM\confucius>javac HelloWorld.java
HelloWorld.java:1: package org.apache.log4j does not exist
import org.apache.log4j.Logger;
^
HelloWorld.java:2: package org.apache.log4j does not exist
import org.apache.log4j.BasicConfigurator;
^
HelloWorld.java:7: cannot find symbol
symbol : variable BasicConfigurator
location: class HelloWorld
BasicConfigurator.configure();
^
HelloWorld.java:8: cannot find symbol
symbol : class Logger
location: class HelloWorld
Logger logger = Logger.getLogger(HelloWorld.class);
^
HelloWorld.java:8: cannot find symbol
symbol : variable Logger
location: class HelloWorld
Logger logger = Logger.getLogger(HelloWorld.class);
^
5 errors

C:\users\LavanniM\confucius>


Not so easy to understand why this time - but it is because we included the /lib directory - but NOT the jar itself (this is always a big source of confusion)

So now try adding the log4j.jar in the CLASSSPATH and this time commpilation will succeed - and the program will run (see below)

 C:\users\LavanniM\confucius>set CLASSPATH=C:\users\LavanniM\confucius;C:\users\LavanniM\confucius\lib\log4j-1.2.16.jar;  

C:\users\LavanniM\confucius>javac HelloWorld.java

C:\users\LavanniM\confucius>java HelloWorld
0 [main] DEBUG HelloWorld - Hello World!

C:\users\LavanniM\confucius>