Model View Controller (MVC) with JSP and JSTL

In this article we will create a small web application that uses the Model View Controller (MVC) pattern with Java Server Pages (JSP) and JSP Standard Template Library (JSTL). A container like Tomcat is needed to run this combination.

Thanks go out to the author of the JSP – MVC Tutorial, who thought of the sample application that we will use here, the coffee advisor. The user is first presented with a choice in coffee taste she prefers. Pressing a button moves on to a page with advise about the type of coffee to drink based on that taste.

An MVC application has three parts:

  • Model. The model is the domain-specific representation of the data upon which the application operates. In our case this is implemented in the CoffeeExpert class.
  • View. The view renders the model into a form suitable for interaction, typically a user interface element. In our case this is implemented in a JSP file called coffee.jsp.
  • Controller. The controller receives input and initiates a response by making calls on model objects. In our case this is implemented in the CoffeeSelect class.

In addition to these three file we need a web.xml file that tells the container how to map a URL (e.g. /CoffeeSelect.do) into a class to run (e.g. com.example.web.CoffeeSelect). We also need a start page for the user input, which we will call coffee.html. In total this means we should create a  .war file with the following structure:

coffee.html

<html>
    <body>
        <h2>Coffee Advisor Input</h2>
        <form method="POST" action="CoffeeSelect.do">
            <select name="taste" size=1">
                <option value="milky">Milky</option>
                <option value="froffy">Froffy</option>
                <option value="icey">Icey</option>
                <option value="strong">Spaced Out</option>
            </select>
            <br/><br/>
            <input type="Submit"/>
        </form>
    </body>
</html>

CoffeeExpert.java

package com.example.model;

import java.util.*;

public class CoffeeExpert {
    public List<String> getTypes(String taste) {
        List<String> result = new ArrayList<String>();
        if (taste.equals("milky")) {
            result.add("Latte");
            result.add("Cappuccino");
        } else if (taste.equals("froffy")) {
            result.add("Latte");
            result.add("Cappuccino");
            result.add("Frappuccino");
        } else if (taste.equals("icey")) {
            result.add("Frappuccino");
        } else if (taste.equals("strong")) {
            result.add("Espresso");
            result.add("Double espresso");
        } else {
            result.add("Vending machine");
        }
        return (result);
    }
}

CoffeeSelect.java

package com.example.web;

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

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.example.model.CoffeeExpert;

public class CoffeeSelect extends HttpServlet {

    private static final long serialVersionUID = 1L;

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        List<String> types = new CoffeeExpert().getTypes(request.getParameter("taste"));
        request.setAttribute("types", types);
        RequestDispatcher view = request.getRequestDispatcher("coffee.jsp");
        view.forward(request, response);
    }
}

coffee.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<html>
    <body>
        <h2>Coffee Advisor Output</h2>
        <c:forEach var="type" items="${types}">
            <c:out value="${type}"/>
            <br />
        </c:forEach>
    </body>
</html>

web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

    <servlet>
        <servlet-name>Coffee</servlet-name>
        <servlet-class>com.example.web.CoffeeSelect</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>Coffee</servlet-name>
        <url-pattern>/CoffeeSelect.do</url-pattern>
    </servlet-mapping>
</web-app>

Changing CPU and memory settings on XenServer VMs

You can change the CPU and memory settings of XenServer virtual machines using the API. XenServer allows you to set the priority of the virtual machine CPU (called weight), set a limit on the amount of CPU the virtual machine can use (called cap) and set the amount of memory the virtual machine gets.

CPU priority

You can set the weight parameter to a number between 1 and 65536. The default value is 256, meaning normal priority. A virtual machine with a weight of 512 will get twice as much CPU as a virtual machine with a weight of 256 on a contended XenServer host. The following code sets the weight to 128, meaning that it will get half as much CPU as a normal virtual machine.

Map<String, String> vcpuParameters = new HashMap<String, String>();
vcpuParameters.put("weight", "128");
vm.setVCPUsParams(connection, vcpuParameters);

CPU limit

You can set the cap parameter to a percentage number. The default value is 0, meaning there is no cap. The value 100 represents one virtual CPU, so half a virtual CPU can be set by using the value 50. The following code sets the cap to 35, meaning that it will not get more than 35 percent of one virtual CPU, even if the physical CPU is not used to its top.

Map<String, String> vcpuParameters = new HashMap<String, String>();
vcpuParameters.put("cap", "35");
vm.setVCPUsParams(connection, vcpuParameters);

Memory

You can set the amount of memory assigned to a virtual machine in bytes. The following code sets the amount of memory to 1 GB (1,073,741,824 bytes).

vm.setMemoryStaticMax(connection, new Long(1073741824));

Installing Sun JDK 6 on Ubuntu 10.04

The Sun JDK can be installed on Ubuntu by adding a repository and installing with apt-get. The following works with the latest versions of both at the time of writing:

sudo add-apt-repository "deb http://archive.canonical.com/ lucid partner"
sudo apt-get update
sudo apt-get install sun-java6-jdk

If the command add-apt-repository is missing, install it with the following command:

sudo apt-get install python-software-properties

With the alternatives system in place, the Java executable is automatically available:

/usr/bin/java -> /etc/alternatives/java
/etc/alternatives/java -> /usr/lib/jvm/java-6-sun/jre/bin/java

Setting up JAVA_HOME can now be done by editing /etc/profile and adding to the bottom:

export JAVA_HOME=/usr/lib/jvm/java-6-sun

Getting CPU, memory, disk and network metrics from XenServer

In a previous article we looked at getting CPU and memory metrics from XenServer. As noted in that article, as of version 5.5 of XenServer, the preferred way of getting virtual machine metrics is through HTTP calls to get RRD XML files. We showed how to revert to the old way of doing things, but in this article we will look into the new, preferred way. This has the added benefit that disk and network metrics are also available, along with a history of metrics.

The first thing to note is that the metrics are not available through the XenServer API. The RRD XML files are stored on the physical machines that currently host the virtual machines. For example, if you have two physical machines (P1 and P2) with two virtual machines running on each of them (V1a, V1b, V2a and V2b), you need to query P1 for the metrics of V1a and V1b and query P2 for the metrics of V2a and V2b. If a virtual machine is not running at the moment, you can get the (old) metrics from the master in the pool.

Each physical machine has an HTTP interface at the following location:

http://machine_name_or_ip/rrd_updates?start=1234567890

The parameter start tells the server to only give metrics starting at this timestamp (seconds since January 1st 1970). The call requires a username and password, which will be asked if you connect through a webbrowser. The following code does this in Java:

Connection connection = new Connection(new URL(MASTER_HOST));
Session.loginWithPassword(connection, USERNAME, PASSWORD, APIVersion.latest().toString());

Map hostRecords = Host.getAllRecords(connection);
for (Host host : hostRecords.keySet()) {
    URL url = new URL("http://" + host.getAddress(connection) + "/rrd_updates?start=" + (System.currentTimeMillis() / 1000 - TIME_WINDOW));
    URLConnection urlConnection = url.openConnection();
    String encoding = new BASE64Encoder().encode((USERNAME + ":" + PASSWORD).getBytes());
    urlConnection.setRequestProperty ("Authorization", "Basic " + encoding);

    String rrdXportData = IOUtils.toString(urlConnection.getInputStream());
}

The code needs two libraries:

  • commons-io-1.4.jar. This package contains the code to get a string containing the data of an inputstream.
  • xenserver-5.5.0-1.jar. This package contains the code to connect to the XenServer pool.

The following constants are used in this piece of code and need to be filled in:

  • MASTER_HOST. This is the IP address or hostname of the master host in the pool.
  • TIME_WINDOW. XenServer will return RRD updates of the last TIME_WINDOW seconds.
  • USERNAME and PASSWORD. These are the credentials for connecting to the master host and the other physical hosts in the pool.

At the end of this piece of code we have the string rrdXportData containing the RRD XML data. Now we need to parse this data to get the metrics we want. The following listing contains the most important parts of this string (edited for clarity):

<xport>
   <meta>
      <start>1273342925</start>
      <step>5</step>
      <end>1273342980</end>
      <rows>13</rows>
      <columns>31</columns>
      <legend>
         <entry>AVERAGE:vm:19ef51bd-2cbc-50d1-3fa2-ad8878699203:cpu0</entry>
         <entry>AVERAGE:vm:19ef51bd-2cbc-50d1-3fa2-ad8878699203:vif_4_tx</entry>
         ...
      </legend>
   </meta>
   <data>
      <row>
         <t>1273342980</t>
         <v>0.0002</v>
         <v>0.0</v>
         ...
      </row>
      <row>
         <t>1273342975</t>
         <v>0.0003</v>
         <v>0.0</v>
         ...
      </row>
      ...
   </data>
</xport>

The following Java code will parse this RRD XML string and make it available in the four variables shown at the top. The variable metricsTimelines contains a hashmap of string to double[]. The string is the metric name (e.g. AVERAGE:vm:19ef51bd-2cbc-50d1-3fa2-ad8878699203:cpu0) and the double[] contains the values for this metric from startTime to endTime with a step size of step. Note that the value at endTime is at position 0 and the value at  startTime is at the end of the array.

int endTime = 0;
HashMap<String, double[]> metricsTimelines = null;
int startTime = 0;
int step = 0;

try {
   DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
   domFactory.setNamespaceAware(true);
   DocumentBuilder builder = domFactory.newDocumentBuilder();
   StringReader stringReader = new StringReader(rrdXportData);
   InputSource inputSource = new InputSource(stringReader);
   Document doc = builder.parse(inputSource);
   stringReader.close();

   ArrayList<ArrayList<String>> dataRows = new ArrayList<ArrayList<String>>();
   ArrayList<String> legends = new ArrayList<String>();
   NodeList xportChildNodes = doc.getDocumentElement().getChildNodes();
   for (int i = 0; i < xportChildNodes.getLength(); i++) {
      Node xportChildNode = xportChildNodes.item(i);
      if (xportChildNode.getNodeName().equals("meta")) {
         NodeList metaChildNodes = xportChildNode.getChildNodes();
         for (int j = 0; j < metaChildNodes.getLength(); j++) {
            Node metaChildNode = metaChildNodes.item(j);
            if (metaChildNode.getNodeName().equals("step")) {
               step = new Integer(metaChildNode.getTextContent()).intValue();
            } else if (metaChildNode.getNodeName().equals("start")) {
               startTime = new Integer(metaChildNode.getTextContent()).intValue();
            } else if (metaChildNode.getNodeName().equals("end")) {
               endTime = new Integer(metaChildNode.getTextContent()).intValue();
            } else if (metaChildNode.getNodeName().equals("legend")) {
               NodeList legendChildNodes = metaChildNode.getChildNodes();
               for (int k = 0; k < legendChildNodes.getLength(); k++) {
                  Node legendChildNode = legendChildNodes.item(k);
                  legends.add(k, legendChildNode.getTextContent());
               }
            }
         }
      } else if (xportChildNode.getNodeName().equals("data")) {
         NodeList dataChildNodes = xportChildNode.getChildNodes();
         for (int j = 0; j < dataChildNodes.getLength(); j++) {
            Node rowNode = dataChildNodes.item(j);
            NodeList rowChildNodes = rowNode.getChildNodes();
            ArrayList<String> dataRow = new ArrayList<String>();
            for (int k = 1; k < rowChildNodes.getLength(); k++) {
               Node rowChildNode = rowChildNodes.item(k);
               dataRow.add(k - 1, rowChildNode.getTextContent());
            }
            dataRows.add(dataRow);
         }
      }
   }

   int nrDataRows = dataRows.size();
   int nrLegends = legends.size();

   metricsTimelines = new HashMap<String, double[]>();
   for (int i = 0; i < nrLegends; i++) {
      metricsTimelines.put(legends.get(i), new double[nrDataRows]);
   }
   for (int i = 0; i < nrLegends; i++) {
      for (int j = 0; j < nrDataRows; j++) {
         double[] values = metricsTimelines.get(legends.get(i));
         values[j] = new Double(dataRows.get(j).get(i)).doubleValue();
      }
   }
} catch (Exception e) {
   e.printStackTrace();
}

Getting XenServer VM metrics in Java

XenServer is a product of Citrix that can run virtual machines on a set of physical hosts called a pool. There is an API that allows you to create programs that interact with the XenServer pool. There are some function calls that return metrics of the virtual machines and the physical hosts.

Let us have a look at retrieving the CPU load of a virtual machine (VM). This can be accomplished with the following API call (assuming that vm is of type com.xensource.xenapi.VM and connection is of type com.xensource.xenapi.Connection):

vm.getMetrics(connection).getVCPUsUtilisation(connection)

However, as many have noted, the answer is always 0.0. As of XenServer version 5.5, the way these metrics are stored has changed. The preferred way to get VM metrics now, is to make a direct HTTP call to the physical hosts. This call will return RRD XML files. This makes things more difficult because now there are multiple calls to make, one to the XenServer API and one call per physical host machine. And one needs to dig into these files to get the CPU load, instead of having the value returned in a single call.

There is a way to make the XenServer API behave like it did in version 5.0 and prior. We start by logging into the console of a physical machine and entering:

xe host-list

Note the UUID values of all the physical host machines. Now enter the following command for each UUID:

xe host-param-set uuid=<uuid found in previous command> other-config:rrd_update_interval=1

Now reboot all physical host machines and the VM metric command will work like a charm.

Using OpenJMS within Eclipse

OpenJMS allows you to use the Java Message Service (JMS) without a full-fledged Java Enterprise Edition container. It is an open source implementation of the JMS 1.1 specification.

We start by installing and starting OpenJMS. On Windows this is done by extracting the .zip file, going to the bin directory and calling startup.bat. On Linux this is done by extracting the .tar.gz file, going to the bin directory and calling startup.sh.

The next part is creating a Java project in Eclipse. Make sure to create the following directories:

  • bin, containing your own Java .class files
  • etc, containing the JNDI configuration file
  • lib, containing all required .jar library files
  • src, containing your own Java .java files

Start by copying all .jar files from the OpenJMS distribution to the lib directory and tell Eclipse to use these .jar files. Also add the etc directory to the build path. This ensures that the JNDI configuration file that we will create shortly is used at runtime.

Now create the JNDI configuration file that will tell our application where to find the running OpenJMS server. Create a file called jndi.properties with the following content:

java.naming.factory.initial=org.exolab.jms.jndi.InitialContextFactory
java.naming.provider.url=tcp://localhost:3035

Finally, we move on to the actual Java code that makes use of this groundwork. We create a message sender that sends a message to the topic topic1. This is a topic name that is available in OpenJMS by default.

MessageSender.java

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;

public class JMSSender {

   public void start() {
      Context context = null;
      Connection connection = null;
      MessageProducer sender = null;
      try {
         context = new InitialContext();
         ConnectionFactory factory = (ConnectionFactory) context.lookup("ConnectionFactory");
         connection = factory.createConnection();
         connection.start();

         Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
         Destination destination = (Destination) context.lookup("topic1");
         sender = session.createProducer(destination);
         sender.send(session.createTextMessage("Hello World!"));
      } catch (Exception e) {
      } finally {
         try {
            if (sender != null) sender.close();
            if (connection != null) connection.close();
            if (context != null) context.close();
         } catch (Exception e) {
         }
      }
   }

   public static void main(String[] args) {
      JMSSender jmsSender = new JMSSender();
      jmsSender.start();
   }
}

MessageReceiver.java

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class JMSReceiver {

   public void start() throws JMSException, NamingException {
      Context context = null;
      Connection connection = null;
      MessageConsumer receiver = null;
      try {
         context = new InitialContext();
         ConnectionFactory factory = (ConnectionFactory) context.lookup("ConnectionFactory");
         connection = factory.createConnection();
         Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
         Destination destination = (Destination) context.lookup("topic1");

         receiver = session.createConsumer(destination);
         receiver.setMessageListener(new MessageListener() {
            public void onMessage(Message message) {
               TextMessage text = (TextMessage) message;
               try {
                  System.out.println("Received message: " + text.getText());
               } catch (JMSException e) {
               }
            }
         });

         connection.start();
      } catch (Exception e) {
      }
   }

   public static void main(String[] args) {
      JMSReceiver jmsReceiver = new JMSReceiver();
      jmsReceiver.start();
   }
}

Now start the JMSReceiver application and then the JMSSender application. If all goes well, the JMSReceiver application should print the text:

Received message: Hello World!

Sample Tomcat application

tomcat

Tomcat is the servlet container that is used in the official Reference Implementation for the Java Servlet and JavaServer Pages technologies. The Java Servlet and JavaServer Pages specifications are developed by Sun under the Java Community Process.

We want to add a custom application to Tomcat called article. The first thing we have to do is edit the file conf/server.xml

<Context path="/article" docBase="article" debug="0" reloadable="true" />

Now we have to make sone directories for our application.

mkdir webapps/article
mkdir webapps/article/WEB-INF
mkdir webapps/article/WEB-INF/classes
mkdir webapps/article/WEB-INF/lib

All *.html and *.jsp go into the directory webapps/article. All *.class files specific to our application go into webapps/article/WEB-INF/classes. All *.class files that are libraries to our application *.class files (typically *.jar files) go into webapps/article/WEB-INF/lib.

Assume we want to have a servlet that writes articles. Now we have to create the file webapps/article/WEB-INF/web.xml.

<servlet>
   <servlet-name>ArticleWriterName</servlet-name>
   <servlet-class>ArticleWriter</servlet-class>
</servlet>
<servlet-mapping>
   <servlet-name>ArticleWriterName</servlet-name>
   <url-pattern>/writer</url-pattern>
</servlet-mapping>

Now we have to create the file webapps/article/WEB-INF/classes/ArticleWriter.java and compile it. After restarting Tomcat, we can access the servlet from http://servername:8080/article/writer.

Sockets in Java

java

TCP provides a reliable, point-to-point communication channel that client-server applications on the Internet use to communicate with each other. To communicate over TCP, a client program and a server program establish a connection to one another. Each program binds a socket to its end of the connection. To communicate, the client and the server each reads from and writes to the socket bound to the connection.

Server

We want to have a client and a server program that can communicate with each other through a socket. The server binds itself to a port number (in our case 1234) and waits for a client connection.

import java.io.*;
import java.net.*;

public class SocketServer {

   int port;

   ServerSocket server = null;
   Socket client = null;

   BufferedReader in = null;
   PrintWriter out = null;

   String input;

   public SocketServer(int port) {
       this.port = port;
   }

   public void listenSocket() {
      try {
         server = new ServerSocket(port);
      } catch (IOException e) {
         System.err.println("Could not listen on port " + String.valueOf(port));
         e.printStackTrace();
         System.exit(-1);
      }

      System.out.println("Opened port " + server.getLocalPort());
      try {
         client = server.accept();
         in = new BufferedReader(new InputStreamReader(client.getInputStream()));
         out = new PrintWriter(client.getOutputStream(), true);
      } catch (IOException e) {
         System.err.println("Could not accept on port " + String.valueOf(port));
         e.printStackTrace();
         System.exit(-1);
      }

      InetSocketAddress clientAddress = (InetSocketAddress)client.getRemoteSocketAddress();
      System.out.println("Connection with client " + clientAddress.getHostName() + " on port " + clientAddress.getPort());
      try {
         input = in.readLine();
         out.println("Pong");
         System.out.println("Input from client: " + input);
      } catch (IOException e) {
         System.err.println("Could not read from port " + String.valueOf(port));
         e.printStackTrace();
         System.exit(-1);
      }

      try {
         client.close();
         server.close();
      } catch (IOException e) {
         System.err.println("Could not close port " + String.valueOf(port));
         e.printStackTrace();
         System.exit(-1);
      }
      System.out.println("Closed port " + String.valueOf(port));
   }

   public static void main(String[] args) {
      SocketServer socketServer = new SocketServer(1234);
      socketServer.listenSocket();
   }
}

Client

The client connects to the server by specifying the hostname and port number of the server (in our case localhost with port 1234).

import java.io.*;
import java.net.*;

public class SocketClient {

   String host;
   int port;

   Socket socket = null;

   BufferedReader in = null;
   PrintWriter out = null;

   String input;

   public SocketClient(String host, int port) {
      this.host = host;
      this.port = port;
   }

   public void listenSocket() {
      try {
         socket = new Socket(host, port);
         out = new PrintWriter(socket.getOutputStream(), true);
         in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
      } catch (Exception e) {
         System.err.println("Could not connect to server " + host + " on port " + String.valueOf(port));
         e.printStackTrace();
         System.exit(-1);
      }

      try {
         out.println("Ping");
         input = in.readLine();
         System.out.println("Input from server: " + input);
      } catch (IOException e) {
         System.err.println("Could not write to or read from server " + host + " on port " + String.valueOf(port));
         e.printStackTrace();
         System.exit(-1);
      }
   }

   public static void main(String[] args) {
      SocketClient socketClient = new SocketClient("localhost", 1234);
      socketClient.listenSocket();
   }
}