Real-time error monitoring in Google app engine

Yesterday, I showed you how your Google app engine application can send a mail whenever an error occurs (an uncaught Exception for example).

Today we are going to see how to monitor your application in real-time using Google talk.

Of course, the Google talk client has a little icon that tells you how many unread emails you have:

But that’s not enough: we want the GAE application to send a “google talk message” each time an error occurs. This can easily be done using the XMPP service.

First, we define a servlet to handle all exceptions:

<servlet>
  <servlet-name>ShowErrorServlet</servlet-name>
  <servlet-class>fr.kubaski.servlet.ShowError</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>ShowErrorServlet</servlet-name>
  <url-pattern>/showerror</url-pattern>
</servlet-mapping>
<error-page>
  <exception-type>java.lang.Exception</exception-type>
  <location>/showerror</location>
</error-page>

And here is the code for the servlet:

public class ShowError extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // note that 't' cannot be null since this servlet is defined as the error handler in web.xml:
    Throwable t = (Throwable) req.getAttribute("javax.servlet.error.exception");
    JID jid = new JID("theadmin@gmail.com");
    String msgBody = "an error occured: " + t.getMessage();
    Message msg = new MessageBuilder()
    .withRecipientJids(jid)
    .withBody(msgBody)
    .build();
    boolean isSent = false;
    XMPPService xmpp = XMPPServiceFactory.getXMPPService();
    if (xmpp.getPresence(jid).isAvailable()) {
      SendResponse status = xmpp.sendMessage(msg);
      isSent = (status.getStatusMap().get(jid) == SendResponse.Status.SUCCESS);
    }
    if (!isSent) {
      // error handling
    }
    getServletContext().getRequestDispatcher("/WEB-INF/jsp/error.jsp").forward(req, resp);
  }
}

In the code sample above, “theadmin@gmail.com” is the email of an administrator of the GAE application: replace it with yours !

Now you need to invite your GAE application from the Google talk client (exactly like you would invite a friend using his email). To do this, just invite [APPLICATION_IDENTIFIER]@appspot.com. In my case, my application is http://atestapplication.appspot.com so I need to invite atestapplication@appspot.com:

Now generate an exception in your application so that the error servlet is invoked and you will receive an instant notification:

Laurent KUBASKI

Advertisements

Error monitoring in Google app engine

Checking the GAE app engine logs each day for error messages can be very boring: what if you could receive a mail each time a non-handled Exception occurs in your Google app engine Java application ?

Well, it’s very easy: first define an generic “error handler” servlet in web.xml:

<servlet>
  <servlet-name>ShowErrorServlet</servlet-name>
  <servlet-class>fr.kubaski.servlet.ShowError</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>ShowErrorServlet</servlet-name>
  <url-pattern>/showerror</url-pattern>
</servlet-mapping>

<error-page>
  <exception-type>java.lang.Exception</exception-type>
  <location>/showerror</location>
</error-page>

And here is the code for the servlet:

public class ShowError extends HttpServlet {
  private final Log log = LogFactory.getLog(getClass());

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    Throwable t = (Throwable) req.getAttribute("javax.servlet.error.exception");
    log.error("error", t);
    try {
      Message msg = newMessage("theadmin@gmail.com", t.getMessage(), getDisplayableThrowable(t));
      Transport.send(msg);
    } catch (Exception e) {
      // we just log the error: this is not a big deal if the mail was not sent
      log.error("error", t);
    }
    getServletContext().getRequestDispatcher("/WEB-INF/jsp/error.jsp").forward(req, resp);
  }

  public static String getDisplayableThrowable(Throwable t) {
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw, true);
    t.printStackTrace(pw);
    String result = sw.toString();
    try {
      sw.close();
    } catch (IOException e) { /* nothing */}
    pw.close();
    return result;
  }

  private Message newMessage(String adminMail, String subject, String body) throws MessagingException {
    Properties props = new Properties();
    Session session = Session.getDefaultInstance(props, null);
    Message msg = new MimeMessage(session);
    // As stated in GAE documentation:
    // "For security purposes, the sender address of a message must be the email address of an administrator for the application"
    msg.setFrom(new InternetAddress(adminMail));
    msg.addRecipient(Message.RecipientType.TO, new InternetAddress(adminMail));
    msg.setSubject(subject);
    msg.setText(body);
    return msg;
  }
}

Note that the mail sending feature does NOT work in “development mode” and that you need to deploy your app in GAE for this to work.

Laurent KUBASKI

Better conditional statements

I don’t know if you are like me, but I really have a hard time reading complex ‘if’ statements with boolean expressions.

For example:

if ("male".equals(person.getGender()) && person.getAge()>=18 && !"french".equals(person.getNationality()) ||
 "female".equals(person.getGender()) && person.getAge()<18 && !"german".equals(person.getNationality()) {
 // do something
 }

If you are like me, my recommendation is to give explicit names to each expression, like this:

boolean isNonFrenchMaleAdult = "male".equals(person.getGender()) && person.getAge()>=18 && !"french".equals(person.getNationality());
boolean isNonGermanFemaleChild = "female".equals(person.getGender()) && person.getAge()<18 && !"german".equals(person.getNationality());

 if (isNonFrenchMaleAdult || isNonGermanFemaleChild) {
 // do something
 }

From there, it might also be interesting to make the conditional expressions part of the Person class to end up with:

if (person.isNonFrenchMaleAdult() || person.isNonGermanFemaleChild()) {
 // do something
 }

You can even go further and combine the 2 methods in a single one:

if (person.isEligibleToDoSomethingVeryInteresting()) {
 // do something
 }

Finally, why not completely move all the logic inside the Person class ?

person.doIt();

Note that in practice, you may not want to move all the logic inside the Person class.

For example, if the ‘if’ statement persists the Person instance in a database, you may not want to put the data access logic in the domain object.

Laurent KUBASKI

Button at bottom of dialog with MigLayout

MigLayout is a powerfull Swing layout manager.

However, when you are used to the standard J2SE layout managers, it can take a while to understand how to do something using MigLayout.

For example, I needed to create this UI:

As you can see, the idea is to leave the OK button at the button of the dialog, even when the dialog is resized.

My initial attempt was to use a fake panel as the third row:

public class TestResize extends JDialog {
protected JPanel contentPane;
public TestResize() {
   super((Dialog) null, "Test resize", true);
   setupUI();
   setContentPane(contentPane);
}
private void setupUI() {
   contentPane = new JPanel(new MigLayout());
   // first row
   contentPane.add(new JLabel("Enter size"), "");
   contentPane.add(new JTextField(""), "grow, pushx, wrap");
   // second row
   contentPane.add(new JLabel("Enter weight"), "");
   contentPane.add(new JTextField(""), "grow, pushx, wrap");
   // third row = fake panel that is allowed to grow
   contentPane.add(new JPanel(), "span 2, grow, pushy, wrap");
   // fourth row = panel with centered button
   JPanel buttonPanel = new JPanel(new MigLayout("", "[center, grow]"));
   buttonPanel.add(new JButton("Ok"), "");
   contentPane.add(buttonPanel, "dock south");
}
public static void main(String[] args) {
   TestResize dialog = new TestResize();
   dialog.pack();
   dialog.setVisible(true);
}
}

This works… but it’s not the correct way to do it using MigLayout: you need to use  the “push” constraint.

public class TestResize extends JDialog {
protected JPanel contentPane;
public TestResize() {
   super((Dialog) null, "Test resize", true);
   setupUI();
   setContentPane(contentPane);
}
private void setupUI() {
   // Layout is constructed with "push" constraint
   contentPane = new JPanel(new MigLayout("", "", "[][]push[]"));
   // first row
   contentPane.add(new JLabel("Enter size:"), "");
   contentPane.add(new JTextField(""), "grow, pushx, wrap");
   // second row
   contentPane.add(new JLabel("Enter weight"), "");
   contentPane.add(new JTextField(""), "grow, pushx, wrap");
   // third row = panel with centered button
   JPanel buttonPanel = new JPanel(new MigLayout("", "[center, grow]"));
   buttonPanel.add(new JButton("Ok"), "");
   contentPane.add(buttonPanel, "dock south");
}
public static void main(String[] args) {
TestResize dialog = new TestResize();
dialog.pack();
dialog.setVisible(true);
}
}

That’s it: no more crappy fake panel ! 🙂

Laurent KUBASKI

getResourceAsStream

You think that java.lang.Class.getResource(String name) is the same as java.lang.ClassLoader.getResource(String name) ? Think again…

The javadoc for java.lang.Class.getResource(String name) states that:

Before delegation, an absolute resource name is constructed from the given resource name using this algorithm:

  • If the name begins with a '/' ('\u002f'), then the absolute name of the resource is the portion of the name following the '/'.
  • Otherwise, the absolute name is of the following form: modified_package_name/name where the modified_package_name is the package name of this object with '/' substituted for '.' ('\u002e').

Now compare this with the javadoc for java.lang.ClassLoader.getResource(String name) :

The name of a resource is a ‘/‘-separated path name that identifies the resource

Yes, this means that when using ClassLoader.getResource(String name), the path must NOT start with a ‘/’, and in the end, these 2 statements are similar:

this.getClass().getResource("/sub/file1.properties");

// note that there is no '/':

this.getClass().getClassLoader().getResource("sub/file2.properties");

Laurent KUBASKI

Searching a class in jar files

OK, you need to import a class named “Logger” and you know that it can be found in ones of these 15752 jar files that you have on your hard drive… but which one ?

There are lots of IDE plugins, or even standalone utilities that can help you find the jar that you need, but nothing is faster than using Agent Ransack.

This is the most rapid search software I’ve ever seen. To search for classes in jar files, simple enter “*.jar” as the file name and your class name in the “containing text” field:

Laurent KUBASKI

JTable tips

Here are 2 common requirements when working with Swing’s JTable:

  • validating the editing of a cell without having to hit the “enter” key
  • preventing a JTable to intercept the “enter key” (so that the default button of your dialog is correctly activated)
Here is a little code sample that illustrates these 2 points:
public class TestTable extends JDialog {
  private JTable table;

  public TestTable() {
  super((Frame) null, "The dialog", true);
  setupUI();
  }

  private void setupUI() {
  table = new JTable();
  DefaultTableModel model = (DefaultTableModel) table.getModel();
  model.setColumnIdentifiers(new Object[]{"myColumn"});
  model.addRow(new String[]{"Row 1 (edit me)"});
  model.addRow(new String[]{"Row 2"});
  getContentPane().add(new JScrollPane(table), BorderLayout.CENTER);
  JButton defaultButton = new JButton("default button");
  defaultButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
    JOptionPane.showMessageDialog(TestTable.this, table.getValueAt(0, 0));
    }
  });
  getRootPane().setDefaultButton(defaultButton);
  getContentPane().add(defaultButton, BorderLayout.SOUTH);
  }

  public static void main(String[] args) {
    TestTable dialog = new TestTable();
    dialog.pack();
    dialog.setVisible(true);
    System.exit(0);
  }
}

Here is the output:


Now if you click on the first row and if you hit the “enter” key, the focus switches to the next row in the JTable (although you defined the “default button” button to be the default button of your dialog):



Now if you start editing the first cell (replacing “edit me” by “edit ME”) and click on the button, this is what happens:



Damn… why does the message box display the old value ? Well, this is because to validate the edition of a cell in a JTable, you are supposed to hit the “enter” key on your keyboard.
Both problems can be solved by calling this method inside the constructor:
private void changeTableProperties() {
  // now hitting "enter" will trigger the "default button" action:
  // (see source code of JTable.CellEditorRemover.propertyChange() method for more information)
  table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
       .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "none");
  // now you can click on the button without having to hit "enter" to finish editing the row:
  // see http://download.oracle.com/javase/tutorial/uiswing/misc/keybinding.html for more information
  table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
  }


From there, hitting the “enter” key will correctly trigger the default button action:



And when editing the cell, you’ll be able to directly click on the button to have your input taken into account (without the need to hit the “enter” key:


Laurent KUBASKI