Implementing a QR servlet in java

In this posting I want to implement a sample application which creates a QR-Code (for any URL) and displays as PNG image in the browser.

As in previous postings I will use Eclipse and integrate the QR-Code in a simple Vaadin 8 application, because it simplifies the workflow. But the code will work with any kind of UI framework.

To create the QR-Code I decided to use the net.glxn.QRGen framework.

Okay let us directley start into the object representation of a QR-Code:

QRCode:

package at.nettania.dev.sample.qrcodeapp.core;

import java.io.ByteArrayOutputStream;
import java.io.Serializable;

import net.glxn.qrgen.image.ImageType;

/**
 * Implementation of QRCode. Use this class to create an arbitrary {@link QRCode} object.
 * Use {@link QRCodeUtil} to display the QRCode as PNG image in the browser.
 * This object is not persistent and will be created on the fly.
 * 
 * @author florian
 *
 */
public class QRCode implements Serializable {
  
  /**
   * 
   */
  private static final long serialVersionUID = 6368834871126835904L;
  
  private String name;
  private String qrCodeValue;
  private int width;
  private int height;
  
  /**
   * Use this constructor to create an empty {@link QRCode} with the dimensions 250x250 and name qrcode
   */
  public QRCode() {
    this.name = "qrcode";
    this.width = 100;
    this.height = 100;
  }
  
  /**
   * Use this constructor to create a {@link QRCode} with the given value and the given name,
   * which is used as filename if displayed in the browser in combination with {@link QRUtil}
   * @param name
   * @param qrCodeValue
   */
  public QRCode(String name, String qrCodeValue) {
    this.name = name;
    this.qrCodeValue = qrCodeValue;
    this.width = 250;
    this.height = 250;
  }
  
  /**
   * @return the name
   */
  public String getName() {
    return name;
  }

  /**
   * @param name the name to set
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * @return the qrCodeValue
   */
  public String getQrCodeValue() {
    return qrCodeValue;
  }

  /**
   * @param qrCodeValue the qrCodeContent to set
   */
  public void setQrCodeValue(String qrCodeValue) {
    this.qrCodeValue = qrCodeValue;
  }

  /**
   * @return the width
   */
  public int getWidth() {
    return width;
  }

  /**
   * @param width the width to set
   */
  public void setWidth(int width) {
    this.width = width;
  }

  /**
   * @return the height
   */
  public int getHeight() {
    return height;
  }

  /**
   * @param height the height to set
   */
  public void setHeight(int height) {
    this.height = height;
  }

  /**
 * Return the QRCode as byte array in UTF-8 and as PNG image
 * @return
 */
 public byte[] getQRCode() {
    ByteArrayOutputStream stream = net.glxn.qrgen.QRCode.from(qrCodeValue).withSize(width, height).withCharset("UTF-8").to(ImageType.PNG).stream();
    return stream.toByteArray();
 }
}

This is a quiet simple class and only used to simplify the method arguments for the QRCodeUtil class. The qrCodeValue string will contain the text form which the QRCode should be generated. The name string is only used as filename for the image. with the two int values for width and height I set the dimension of the QRCode. This could also be merged into one integer, because a QRCode should usually be quadratic. Of course, creating an own QRCode object might be oversized for this example, but I personally always like to work with objects instead of methods with multiple parameters.

The key method of this class and also from the whole application is the last public byte[] getQRCode() method. In this method, we use the QRGen framework to create an QRCode and return it as png UTF-8 byte array. Later we will use this method in the servlet class to create a BufferedImage.

Our next class will be a static utility class providing methods to quickly create an QRCode.

QRCodeUtil:

package at.nettania.dev.sample.qrcodeapp.core;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This class implements static utility methods for {@link QRCode}
 * 
 * @author florian
 *
 */
public class QRCodeUtil {
  
  private static Log log = LogFactory.getLog(QRCodeUtil.class);
  
  public static String getQrCodeUrl(String url, String qrCodeContent) {
    QRCode code = new QRCode();
    code.setName("myqrcode");
    code.setQrCodeValue(qrCodeContent);
    code.setHeight(250);
    code.setWidth(250);
    
    return getQRCodeUrl(url, code);
  }

  /**
   * Returns the URL to display this {@link QRCode} in the browser
   * @param qrcode
   * @return
   */
  public static String getQRCodeUrl(String url, QRCode qrcode) {
    String uurl = "";
    try {
      uurl = url+"qrcode/{filename}.png?code={qrcode}&dimension={width}x{height}";
      uurl = uurl.replace("{filename}", qrcode.getName());
      uurl = uurl.replace("{qrcode}", URLEncoder.encode(qrcode.getQrCodeValue(), "UTF-8"));
      uurl = uurl.replace("{width}", qrcode.getWidth()+"");
      uurl = uurl.replace("{height}", qrcode.getHeight()+"");
      log.debug("URL: "+uurl);
    } catch (UnsupportedEncodingException e) {
      log.error("Unexpected error occured.", e);
    }
    
    return uurl;
  }
}

This class provides two methods which creates a QRCode and returns the URL to its image representation. The getQrCodeUrl(String url, String qrCodeContent) method requires the base URL of the application and a String with the content for the QRCode. With this information the method creates a QRCode object and calls the second getQRCodeUrl(String url, QRCode qrcode) method of this class.

Let us go a little bit into detail of the getQRCodeUrl(String url, QRCode qrcode) method, where we assemble the URL. In line 37 we define the url pattern for the servlet, consisting of the application url, the servlet extension (qrcode), the filename an file type (.png) as well as the GET parameters for the servlet. code: The String which should be displayed as QRCode, and the dimension: Defines the width and height of the created QRCode (separated by x).

The next code lines only replaces the defined placeholders with data. Line 39 should be considered where we encode the QR-Code to UTF-8, which is important to avoid encoding problems.

Finally, we return the created URL as String.

QRCodeServlet:

This class is a simple servlet which creates form a ByteArray an image.

package at.nettania.dev.sample.qrcodeapp.web;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import at.nettania.dev.sample.qrcodeapp.core.QRCode;

/**
 * This class implements a servlet to display a QRCode
 * 
 * @author florian
 *
 */
@WebServlet(urlPatterns = "/qrcode/*", name = "QRCodeServlet", asyncSupported = true)
public class QRCodeServlet extends HttpServlet {

  /**
   * 
   */
  private static final long serialVersionUID = -6031742898289759780L;
  private static Log log = LogFactory.getLog(QRCodeServlet.class);
  
  public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
    int width = 250;
    int height = 250;
    
    String filename = req.getPathInfo();
    String code = req.getParameter("code");
    String dimension = req.getParameter("dimension");
    if(dimension != null && dimension.matches("\\d+x\\d+")) {
      width = Integer.parseInt(dimension.split("x")[0]);
      height = Integer.parseInt(dimension.split("x")[1]);
    }
    
    
    if(code == null) {
      log.debug("Code parameter is missing.");
      res.sendError(HttpServletResponse.SC_BAD_REQUEST);
    } else {
      log.debug("Creating QRCode for filename "+filename);
      
      QRCode qrCode = new QRCode();
      qrCode.setWidth(width);
      qrCode.setHeight(height);
      qrCode.setQrCodeValue(code);
      qrCode.setName(filename);
      
      
      ByteArrayInputStream in = new ByteArrayInputStream(qrCode.getQRCode()); // The input Stream
      BufferedImage image = ImageIO.read(in);
      getImage(image, qrCode.getName(), qrCode.getFiletype(), res);
    }
  }
  
  private static void getImage(BufferedImage image, String filename, String type, HttpServletResponse res){
    try {
      String formatName = "JPEG";
      if(filename.contains(".")) {
        String[] split = filename.split("\\.");
        formatName = split[split.length-1];
      }
      
      log.debug("Filename: "+filename);
      log.debug("Type of image: "+formatName);
      
      // As default type we use RGB, because if the JPEG is of type CMYK a bug in Java leads to a wrong representation of the colors. 
      // RBG does not support transparency, but JPEG does not do it either, therefore it does not matter that we have no alpha channel.
      int imageType = BufferedImage.TYPE_INT_RGB;
      
      // We change the image type to ARGB to support transparent backgrounds for PNG
      if(formatName.toLowerCase().equals("png"))
        imageType = BufferedImage.TYPE_INT_ARGB;
      // We change the image type to ARGB to support transparent backgrounds for GIF
      if(formatName.toLowerCase().equals("gif"))
        imageType = BufferedImage.TYPE_INT_ARGB;
      
      
        BufferedImage convertedImage = new BufferedImage(image.getWidth(), image.getHeight(), imageType);
        convertedImage.getGraphics().drawImage(image, 0, 0, null);
        convertedImage.getGraphics().dispose();
        
      // Write the image with the java's ImageIO
      ImageIO.write(convertedImage, formatName, res.getOutputStream());
    }
    // if something happens an exception is thrown
    catch(Exception e){
      log.error(e);
    }
  }
}

We use annotations instead of web.xml file to define the settings of the servlet. This servlet has the name QRCodeServlet and “/qrcode/*” as relative path.

The doGet method uses the GET parameters to create a new instance of a QRCode object. If the dimension parameters do not exist, the servlet defines a default width and height of 250. If the code parameter does not exist the servlet throws an http 400 error, which means bad request.

Then we create an ByteArrayInputStream which contains the ByteArray representation of our QRCode. With the help of the static ImageIO class we create a BufferedImage and call the static getImage(BufferedImage image, String filename, String type, HttpServletResponse res) method to write the Image and display it as servlet content.

As already mentioned at the beginning I integrated this sample into a Vaadin 8 application, consisting of a TextField a Button and an Image (which displays the QRCode) to have a working example. The following code represents the Vaadin UI:

/**
 * This is a sample application which creates from a {@link TextField} input a QR-Code.
 * 
 * @author florian
 *
 */
@Theme("mytheme")
public class QrCodeUi extends UI {

    /**
   * 
   */
  private static final long serialVersionUID = 1633256676992649404L;

  @Override
    protected void init(VaadinRequest vaadinRequest) {
    Page.getCurrent().setTitle("QR-Code sample");
        
        //
        // UI components
        //
    final VerticalLayout layout = new VerticalLayout();
        final Image qrImage;
        
        //
        // Wrapper around the TextField
        //
        final HorizontalLayout wrapper = new HorizontalLayout();
        wrapper.setWidthUndefined();
        layout.addComponent(wrapper);
        layout.setComponentAlignment(wrapper, Alignment.TOP_CENTER);
        
        final Label caption = new Label("Enter an URL to display as QRCode:");
        wrapper.addComponent(caption);
        wrapper.setComponentAlignment(caption, Alignment.MIDDLE_CENTER);
        
        final TextField name = new TextField();
        name.setWidth(250, Unit.PIXELS);
        name.setPlaceholder("www.florian-rhomberg.net");
        wrapper.addComponent(name);
        name.focus();
        
        final Button button = new Button("Create QR-Code");
        wrapper.addComponents(button);
        
        //
        // The QR-Image
        //
        qrImage = new Image();
        layout.addComponent(qrImage);
        layout.setComponentAlignment(qrImage, Alignment.TOP_CENTER);
        
        button.addClickListener(new ClickListener() {

      /**
       * 
       */
      private static final long serialVersionUID = 7507056868576045294L;

      @Override
      public void buttonClick(ClickEvent event) {
              qrImage.setSource(new ExternalResource(QRCodeUtil.getQrCodeUrl(Page.getCurrent().getLocation().toString(), name.getValue())));
      }});
        
        setContent(layout);
    }

    @WebServlet(urlPatterns = "/*", name = "QrCodeUiServlet", asyncSupported = true)
    @VaadinServletConfiguration(ui = QrCodeUi.class, productionMode = false)
    public static class QrCodeUiServlet extends VaadinServlet {

    /**
     * 
     */
    private static final long serialVersionUID = 3594396700811845967L;
    }
}

That’s it, I hope this posting might help someone who also needs to implement QRCode in a java web applications. The whole project is available on github.

Links:

Leave a Reply

Your email address will not be published. Required fields are marked *