Java tutorial

This tutorial describes how you can provide and consume Qworum services with Java servlets 2.5 and JavaServer Pages (JSP) 2.2.

The web application presented here is available for download:

Browse the documentation for in-depth information about Qworum.

Note: This tutorial is somewhat out of date, because it makes intensive use of XML messaging which needlessly increase the traffic between web browsers and servers.
Instead, we recommend the use of JavaScript and HTML bindings for implementing and calling interactive services. Also, XML capability is not available on all Qworum browser add-ons.

Boilerplate

In order to use Qworum, your application only needs to be able to:

To start with, implement utility methods for receiving, reading, and generating XML documents, as well as using JSP templates as views:

// src/ApplicationServlet.java
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import javax.xml.transform.dom.*; 
import javax.xml.xpath.*;
import org.xml.sax.*;
import org.w3c.dom.*;

public class ApplicationServlet extends HttpServlet {
  // ...

  // Utilities

  // Generate a response using a JSP file
  protected void view(HttpServletRequest request, HttpServletResponse response){
    try{
      request.getRequestDispatcher("/views"+request.getRequestURI()+".jsp").forward(request, response);
    }catch(Exception ex){}
  }

  // Generate a Qworum message containing a fault
  protected void viewFault(String title, HttpServletResponse response){
    try{
      Document doc=createDocument();
      doc.appendChild(doc.createElementNS(QWORUM_NS, "fault"));
      if(title != null){
        Element titleElement = doc.createElementNS(QWORUM_NS, "title");
        titleElement.appendChild(doc.createTextNode(title));
        doc.getDocumentElement().appendChild(titleElement);
      }

      response.setContentType("application/xml");
      serializeDocument(doc,response.getWriter());
    }catch(Exception ex){}
  }
  protected final String QWORUM_NS="http://qworum.net/";

  // Read an XML document in the body of a POST request
  protected Document parsePostedXml(HttpServletRequest request){
    Document doc = null;
    try{
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder parser = factory.newDocumentBuilder();
      InputSource inputSource=null;
      if(request.getContentType().equals("application/xml")){
        inputSource = new InputSource(request.getInputStream());
      }else{
        inputSource = new InputSource(new StringReader(request.getParameter("qworum")));
      }
      doc=parser.parse(inputSource);
    }catch (Exception ex){
      ex.printStackTrace();
    }
    return doc;
  }

  // Generate an XPath expression
  protected XPathExpression createXPath(String expression){
    XPathExpression res = null;
    try{
      XPath xpath = XPathFactory.newInstance().newXPath();
      res = xpath.compile(expression);
    }catch(Exception ex){}
    return  res;
  }

  // Create an empty XML document
  protected Document createDocument(){
    Document doc=null;
    try{
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder builder = factory.newDocumentBuilder();
      DOMImplementation impl = builder.getDOMImplementation();
      doc=impl.createDocument(null,null,null);
    }catch(Exception ex){}
    return doc;
  }

  // Write an XML document
  protected boolean serializeDocument(Document doc, PrintWriter out){
    try{
      DOMSource domSource = new DOMSource(doc);
      StreamResult streamResult = new StreamResult(out);
      TransformerFactory tf = TransformerFactory.newInstance();
      Transformer serializer = tf.newTransformer();
      serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"yes");
      serializer.setOutputProperty(OutputKeys.INDENT,"yes");
      serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
      serializer.transform(domSource, streamResult); 
    }catch(Exception ex){
      return false;
    }
    return true;
  }
}

Then edit the web.xml file as follows:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- webapp/WEB-INF/web.xml -->
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   version="2.5"> 
  <display-name>Demo</display-name>
  <servlet>
    <servlet-name>app</servlet-name>  
    <servlet-class>ApplicationServlet</servlet-class>  
  </servlet>
  <servlet-mapping>
    <servlet-name>app</servlet-name>
    <url-pattern>/helloworld_demo/app/*</url-pattern>
  </servlet-mapping>
  
  <servlet>
    <servlet-name>service</servlet-name>  
    <servlet-class>QworumServiceServlet</servlet-class>  
  </servlet>
  <servlet-mapping>
    <servlet-name>service</servlet-name>
    <url-pattern>/helloworld_demo/service/*</url-pattern>
  </servlet-mapping>
</web-app>

Providing a Qworum service

Implement a service that receives a call argument, computes a result, shows the result to the end-user, and returns the result.

Here is a servlet that implements that service:

// src/QworumServiceServlet.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.w3c.dom.*;
import javax.xml.xpath.XPathConstants;

public class QworumServiceServlet extends ApplicationServlet {
  public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
    try{
      String path=request.getPathInfo();
      Document doc = parsePostedXml(request);
      if(doc==null)throw new Exception("Phase parameter could not be parsed.");
      if(path==null){
        throw new Exception("No such phase.");
      }else if(path.equals("/index")){// Phase 1
          String name=((String)createXPath("/name/text()").evaluate(doc, XPathConstants.STRING)).trim();
          if(name.length()==0)throw new Exception("Call argument has wrong format.");
          viewSetResult(name, response);
      }else if(path.equals("/show")){// Phase 2
        String sentence=((String)createXPath("/sentence/text()").evaluate(doc, XPathConstants.STRING)).trim();
        request.setAttribute("result", sentence);
        view(request, response);
      }else{
        throw new Exception("No such phase.");
      }
    }catch(Exception ex){
      viewFault(ex.getMessage(), response);
    }
  }

  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    try{
      String path=request.getPathInfo();
      if(path==null){
        throw new Exception("No such phase.");
      }else if(path.equals("/return")){// Phase 3
        view(request, response);
      }else{
        throw new Exception("No such phase.");
      }
    }catch(Exception ex){
      viewFault(ex.getMessage(), response);
    }
  }

  // views

  private void viewSetResult(String name, HttpServletResponse response){
    try{
      Document doc=createDocument();
      Element goTo = doc.createElementNS(QWORUM_NS, "qrm:goto"); doc.appendChild(goTo);
      goTo.setAttribute("href", "show");
      Element variable = doc.createElementNS(QWORUM_NS, "qrm:variable"); goTo.appendChild(variable);
      variable.setAttribute("name", "result");
      Element sentence=doc.createElement("sentence"); variable.appendChild(sentence);
      sentence.appendChild(doc.createTextNode("Hello, "+name+"."));

      response.setContentType("application/xml");
      serializeDocument(doc,response.getWriter());
    }catch(Exception ex){}
  }
}

And here is are the views for show and return phases of that service:

<!DOCTYPE html>
<!-- webapp/views/helloworld_demo/service/show.jsp -->
<html>
<head>
  <title>Qworum Service</title>
</head>
<body style='background: silver; margin: 20%'>
  <h1>Qworum Service say_hello</h1>
  <p>The current call will return:  "${result}"</p>
  <p style='font-size: 70%'><a href="return">Click to return</a></p>
</body>
</html>
<!DOCTYPE html>
<%@ 
  page contentType="application/xml" 
%><!-- webapp/views/helloworld_demo/service/return.jsp -->
<variable name='result' xmlns='http://qworum.net/' />

Phase 3 is unnecessary if the browser provides a qworum JavaScript object. So phase 2 can be rewritten as follows in order to reduce server workload:

<!DOCTYPE html>
<!-- webapp/views/helloworld_demo/service/show.jsp -->
<html>
<head>
  <title>Qworum Service</title>
</head>
<body style='background: silver; margin: 20%'>
  <h1>Qworum Service say_hello</h1>
  <p>The current call will return:  "${result}"</p>
  <button id='return'>Return</button>

  <script>
    var button = document.getElementById('return');
    button.addEventListener('click', function(){
       evaluateQworumMessage(['variable','result'], 'return');
    });

    function evaluateQworumMessage(msg, msgUrl){
      if(typeof qworum == 'object')if(typeof qworum.eval == 'function'){
        qworum.eval(msg);
        return;
      }
      window.location = msgUrl;
    }
  </script>
</body>
</html>

Consuming a Qworum service

In order to call the service above, your site needs to generate a Qworum message that contains a call instruction. Implement that with a view for a second servlet, as follows:

<%@ 
  page contentType="application/xml" 
%><!-- webapp/views/helloworld_demo/app/call_service.jsp -->
<!-- 
  QWORUM SUPPORT IS MISSING IN YOUR BROWSER:
  This site only works correctly with Qworum enabled browsers. 
  Please visit  http://www.qworum.com/products
 -->
<qrm:goto href='receive_call_result' xmlns:qrm='http://qworum.net/'>
  <qrm:call href='/helloworld_demo/service/index'>
    <name>Dave</name>
  </qrm:call>
</qrm:goto>

It is good practice to include a comment such as the one above in Qworum messages generated by web applications. This is obviously not needed for messages generated by Qworum services.

The goto instruction in that message specifies the URL where the call result is to be posted. Here is a view for that URL:

<!DOCTYPE html>
<!-- webapp/views/helloworld_demo/app/receive_call_result.jsp -->
<html>
<head>
  <title>Application</title>
</head>
<body style='margin: 20%'>
  <h1>Application</h1>
  <p>Result received from say_hello service is: ${sentence}</p>
  <p style='font-size: 70%'><a href="/site">Go to home page</a></p>
</body>
</html>

And here are the doGet and doPost methods for the caller servlet:

// src/ApplicationServlet.java 
//...
public class ApplicationServlet extends HttpServlet {
  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String path=request.getPathInfo();
    if(path==null){
      response.sendError(404);
    }else if(path.equals("/index") || path.equals("/call_service")){
      view(request, response);
    }else{
      response.sendError(404);
    }
  }

  public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
    try{
      String path=request.getPathInfo();
      if(path==null){
        response.sendError(404);
      }else if(path.equals("/receive_call_result")){
        Document doc=parsePostedXml(request);
        String sentence=((String)createXPath("/sentence/text()").evaluate(doc, XPathConstants.STRING)).trim();
        request.setAttribute("sentence", sentence);
        view(request, response);
      }else{
        response.sendError(404);
      }
    }catch(Exception ex){}
  }

  // Utilities
  //...
}

And the start page:

<!DOCTYPE html>
<!-- webapp/views/helloworld_demo/app/index.jsp -->
<!-- URL: http://HOST:PORT/helloworld_demo/app/index -->
<html>
<head>
  <title>Application</title>
</head>
<body style='margin: 20%'>
  <h1>Application</h1>
  <p><a href="call_service">Call say_hello service with parameter "Dave"</a></p>
</body>
</html>

User experience

You should observe the following: