pátek 25. listopadu 2005

Java včera, dnes a zítra

Dnes bych chtěl pár řádek věnovat Jave 5.0. Je to až s podivem, jak zpátečnický jsem si připadal, když jsem se na těchto stránkách skepticky rozepisoval o přechodu na Javu 5.0 viz Migrace na Javu 5.0, realita nebo utopie? (5.10.2004) a Přejít na Javu 5.0, ANO či NE? (30.5.2005). Od té doby už uběhlo hodně času a já jsem si ten přechod mohl prodělat na vlastní kůži.

Nyní už si tak zpátečnický nepřipadám a dokonce si troufám tvrdit, s odstupem času, že to byl docela racionální pohled. Tou největší změnou, kterou Java prodělala, bylo zavedení generických typů.

Ach jak povrchní byly všichni ti pisálkové, včetně mě samotného, jenž referovali o zavedení generik. Profláknutý příklad s generickou kolekcí budiž toho důkazem. Generiky rozšiřují sémantiku jazyka do takové míry, že pokud si je nenastudujete, budete si pravděpodobně připadat jako Alenka v říši divů. A věřte mi, že ne jenom při čtení zdrojového kódu.

Všechny ostatní vlastnosti, které pětková řada představila, jsou jenom kosmetickým vylepšením. Generiky změnily nebo posunuly jazyk úplně jinam. Jestliže budete chtít pochopit a používat generiky v plné síle, nebude Vám k tomu možná stačit ani fenomenální Generics FAQ (na papíře to děla 300 stránek) od Angeliky Langer.

Kromě toho, že budete muset načerpat mnoho pojmů jako raw type, wildcard, wildcard capture, type erasure, type argument inference, generická třída, generická metoda (mezi těmi je diametrální rozdíl). Také budete muset například pochopit, že je generická třída a generická třída konkrétního typu, a že mezi třídami konkrétních generických typů není dědičnost (jsou to nekompatibilní typy).

Podobných pastí a pastiček, kterými si budete muset prolézt, není zrovna málo. Možná to dojde tak daleko, že abyste pochopili a nebo realizovali použití generiky, bude se muset vžít do role kompilátoru. Apropo, když jsem u toho kompilátoru, jak si myslíte, že se zachová v následujícím případě, zkompiluje nebo nezkompiluje?


public class Foo {
    
  public static <T> T doSomething(){
   return doSomethingII();
  }
    
   
  public static <U> U doSomethingII(){
   return (U) new Object();
  } 
}

Pokud si myslíte, že zkompiluje, tak máte pravdu. Vy, co si myslíte, že to nezkompiluje, máte ovšem také pravdu! Záleží na tom, jaký kompilátor použijete. Pokud použijete kompilátor ze Sun SDK (testováno na verzi 1.5.0_04 a 1.5.0_05) tak máte smůlu. Pokud použijete Jikes kompilátor máte vyhráno (používá jej například IDE Eclipse nebo Tomcat).

Mimochodem, víte jak ten kód upravit tak, aby jej "baštil" i kompilátor ze Sun SDK? Je povelená úprava pouze na úrovni generik.

Tak taková je Java dnes. Každý nechť si položí otázku, jestli opravdu potřebuje a využije vlastnosti pětkové verze a jestli to pro něj nebude v důsledku spíše kotnraproduktivní. Když jsem nad tímhle zamyšlením dumal, inspirací mi byl pohled Eda Burnetta prezentovaný v článku Better, faster, stupider Java

Mylar is a plug-in for Eclipse and, being written from scratch, the developers decided to use all the newest features of JSE 5. One little problem, not everybody could use JSE 5. For example there is no JSE 5 support on RedHat Linux with GCJ. Oh, there probably will be someday, and maybe there are workarounds with retroweaver or something but that's not the point.

Language guys love to tweak. I know, because I'm a language guy myself. I used to work on compilers, code generators, libraries, and debuggers. We were always adding new stuff, new commands, special keywords, etc. Did our users thank us for it? Maybe the one or two that wanted the new things, but the vast majority would just groan when a new release came out. What will this break now? Will I have to upgrade? Will I have to use somebody else's code that requires this new thing before I'm ready? Has it been ported to all the machines I need to run on? Boring, I know, but very practical and important issues.

A jaká bude Java zítřejška?

středa 23. listopadu 2005

Simple protection for web forms with Jcaptcha and Spring MVC

Time to time we need web forms protection before robot submision for example spammers, DOS attack and so on. The saftest and commonly used way how distinguish between robot and human is CAPTCHA. CAPTCHA is acronym for Completely Automated Public Test to tell Computers and Humans Apart.

In context web form is CAPTCHA presented as pair which contain dynamicly generated picture (image challenge) and input box. Befeore each submit of form user has to retype text from picture to input box. This text is validated on server side and if is correct form submission continue otherwise original form is returned.

In Java world exists open source CAPTCHA solution be called Jcaptcha. Jcaptcha will be very easly integrated to any web application and has a lot of prearranged modules e.g. for Struts or Servlet filter. In this article we show how to integrated Jcaptcha with Spring MVC.

What we need?

At first, dynamicly generate CAPTCHA image and second, control user retyped text during form submission. We can use Jcaptcha Base module for this. Base module aims to provide base components to build a specialized module.

The heart of JCaptcha is CaptchaService or more precisely ImageCaptchaService and their specialized implementation. This interface defines two main method getImageChallengeForID and validateResponseForID.

  • getImageChallengeForID(String ID) - method to retrive the image challenge corresponding to the given ticket.
  • validateResponseForID(String ID, Object response) - method to validate a response to the image challenge corresponding to the given ticket.

We get session id as ticket (ID) that will identify the generated captcha. Session id is unique for client and automaticly is sending by browser with each request. We needn`t stress with generating unique number a handling new parameter.

Image genarating will be wrapped to controller from Spring MVC point of view.

public class JCaptchaController implements Controller, InitializingBean{
  private ImageCaptchaService captchaService;

  /**
   @see org.springframework.web.servlet.mvc.Controller#handleRequest(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
   */
  public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse responsethrows Exception {
        byte[] captchaChallengeAsJpeg = null;
        // the output stream to render the captcha image as jpeg into
        ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
        
        // get the session id that will identify the generated captcha. 
        //the same id must be used to validate the response, the session id is a good candidate!
        String captchaId = request.getSession().getId();
        
        // call the ImageCaptchaService getChallenge method
        BufferedImage challenge =
                        captchaService.getImageChallengeForID(captchaId,request.getLocale());
        
        // a jpeg encoder
        JPEGImageEncoder jpegEncoder =
                        JPEGCodec.createJPEGEncoder(jpegOutputStream);
        jpegEncoder.encode(challenge);
        

        captchaChallengeAsJpeg = jpegOutputStream.toByteArray();

        // flush it in the response
        response.setHeader("Cache-Control""no-store");
        response.setHeader("Pragma""no-cache");
        response.setDateHeader("Expires"0);
        response.setContentType("image/jpeg");
        ServletOutputStream responseOutputStream =
        response.getOutputStream();
        responseOutputStream.write(captchaChallengeAsJpeg);
        responseOutputStream.flush();
        responseOutputStream.close();
        return null;
  }

  /**
   * Set captcha service
   @param captchaService The captchaService to set.
   */
  public void setCaptchaService(ImageCaptchaService captchaService) {
        this.captchaService = captchaService;        
  }  
  
  /**
   @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
   */
  public void afterPropertiesSet() throws Exception {
        if(captchaService == null){
          throw new RuntimeException("Image captcha service wasn`t set!");
        }
  }
}
Java2html

As you can see, this is classic controller with direct writing to response. It is possible to set any type of ImageCaptchaService implementation through setCaptchaService method. Now we need integrate captcha validation. The good choice is extends SimpleFormController from Spring and override onBindAndValidate method.

This method delegates call to new method validateCaptcha. validateCaptcha is responsible for validation. If is captcha response invalid, new ObjectError is added to validation errors holder and form submission is cancelled.

public class ProtectedFormController extends SimpleFormController {
  /**
   * Default paramter name for CAPTCHA response in <code>{@link HttpServletRequest}</code>
   */
  private static final String DEFAULT_CAPTCHA_RESPONSE_PARAMETER_NAME = "j_captcha_response";
  
  protected ImageCaptchaService captchaService;
  protected String captchaResponseParameterName = DEFAULT_CAPTCHA_RESPONSE_PARAMETER_NAME;
        
  /**
   * Delegates request to CAPTCHA validation, subclasses which overrides this 
   * method must manually call <code>{@link #validateCaptcha(HttpServletRequest, BindException)}</code>
   * or explicitly call super method.
   
   @see #validateCaptcha(HttpServletRequest, BindException)
   @see org.springframework.web.servlet.mvc.BaseCommandController#onBindAndValidate(javax.servlet.http.HttpServletRequest, java.lang.Object, org.springframework.validation.BindException)
   */
  @Override
  protected void onBindAndValidate(HttpServletRequest request, Object command, BindException errorsthrows Exception {        
        validateCaptcha(request, errors);
  }
  
  /**
   * Validate CAPTCHA response, if response isn`t valid creates new error object 
   * and put him to errors holder.
   
   @param request current servlet request
   @param errors errors holder
   */
  protected void validateCaptcha(HttpServletRequest request, BindException errors){
        boolean isResponseCorrect = false;
        
        //remenber that we need an id to validate!
        String captchaId = request.getSession().getId();
        //retrieve the response
        String response = request.getParameter(captchaResponseParameterName);
        //validate response
        try {          
          if(response != null){
                isResponseCorrect =
                  captchaService.validateResponseForID(captchaId, response);
          }
        catch (CaptchaServiceException e) {
                //should not happen, may be thrown if the id is not valid          
        }
        
        if(!isResponseCorrect){
          //prepare object error, captcha response isn`t valid
                  String objectName = "Captcha";
          String[] codes = {"invalid"};
          Object[] arguments = {};
          String defaultMessage = "Wrong cotrol text!";
          ObjectError oe = new ObjectError(objectName, codes, arguments, defaultMessage);
          errors.addError(oe);
        }                 
  }

  /**
   * Set captcha service
   @param captchaService the captchaService to set.
   */
  public void setCaptchaService(ImageCaptchaService captchaService) {
        this.captchaService = captchaService;
  }

  /**
   * Set paramter name for CAPTCHA response in <code>{@link HttpServletRequest}</code>
   @param captchaResponseParameterName the captchaResponseParameterName to set.
   */
  public void setCaptchaResponseParameterName(String captchaResponseParameterName) {
        this.captchaResponseParameterName = captchaResponseParameterName;
  }
}
Java2html

Now we are ready to use Spring and Jcaptcha. I choose sending comment for usage demonstration. Comment is represent as JavaBean and form controller is subclass of ProtectedFormController that has captcha validation.

public class Comment {
  private String email;
  private String subject;
  private String body;
  
  //Getters and Setters
}
Java2html

public class NewCommentForm extends ProtectedFormController {
  
  /**
   @see org.springframework.web.servlet.mvc.SimpleFormController#doSubmitAction(java.lang.Object)
   */
   @Override
    protected void doSubmitAction(Object commandthrows Exception {
        Comment comment = (Commentcommand;
        //do something with new comment for example save to database.
    }   
}
Java2html

Both controller and other stuff are configured in *-servlet.xml.

 
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans> 
 <bean id="formController" class="cz.sweb.pichlik.springtutorial.captcha.NewCommentForm">     
     <property name="captchaService"><ref bean="captchaService"/></property>
     <property name="commandClass"><value>cz.sweb.pichlik.springtutorial.captcha.Comment</value></property>
     <property name="formView"><value>form</value></property>
  <property name="successView"><value>submit</value></property>
  <property name="commandName"><value>comment</value></property>
    </bean>   
    
    <!-- This controller generates CAPTCHA image -->
    <bean id="captchaController" class="cz.sweb.pichlik.springtutorial.captcha.JCaptchaController">
     <property name="captchaService"><ref bean="captchaService"/></property>
    </bean> 
       
   <bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">    
  <property name="mappings">
   <props>    
    <prop key="/newcomment.htm">formController</prop>
    <prop key="/captcha.htm">captchaController</prop>  
   </props>
  </property>
 </bean>
  
 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property>
        <property name="prefix"><value>/WEB-INF/jsp/</value></property>
        <property name="suffix"><value>.jsp</value></property>
    </bean>
</beans>
 

Application context configuration contains only captchaService bean. This bean is configured as singleton and as concrete captcha service implementation is selected default Jcaptcha class DefaultManageableImageCaptchaService>.


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans> 
 <!-- CAPTCHA SERVICE DEFINITION -->
 <bean id="captchaService" 
  class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService" 
  singleton="true"/>
</beans>

HTML code is placed in form.jsp. You can notice how is JCaptchaController called by URL in src attribute of img element. forEach loop print any errors occured during validation (including captcha invalid response text).


<%@ page session="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<html>
 <head>
  <title>New comment</title>
 </head>
 <body>
  <form method="post" action="newcomment.htm">
  <spring:bind path="comment">
   <ul>
    <c:forEach items="${status.errorMessages}" var="errorMessage">
     <li style="color:red"><c:out value="${errorMessage}"/></li>
    </font>   
   </c:forEach>
   </ul>
   <table> 
    <tr>
     <td><label>Email</label></td><td><input type="text" name="email" value="<c:out value="${comment.email}"/>"/></td>
    </tr> 
    <tr>
     <td><label>Subject</label></td><td><input type="text" name="subject" value="<c:out value="${comment.subject}"/>"/></td>
    </tr>
    <tr>
     <td><label>Body</label></td><td><textarea name="body" cols="15" rows="5"><c:out value="${comment.body}"/></textarea></td>
    </tr>
    <tr>
     <td><label>Control text</label></td><td><input type="text" name="j_captcha_response" /></td> 
    </tr>
    <tr>
     <td colspan="2"><img src="captcha.htm" /></td>
    </tr>
    <tr>
     <td colspan="2" align="center"><input type="submit" value="submit" /></td>
    </tr>       
   </table>
   </spring:bind>
  </form>  
 </body>
</html>

New form

Valid submission

Invalid submission

Download source code (80 KB) or source code and libraries (3,2 MB)