mercredi 30 juillet 2008

Oil Addiction

Le prix du pétrole ne cesse de monter sur fond de crise financière. Pour ceux qui n'ont pas bien saisie la situation je vous recommande cette excellente vidéo :

lundi 21 juillet 2008

Jersey REST (JSR-311) and JSON part II

In a previous post we have seen how to create a REST resource which produces a JSON or XML output with jaxb and jettison. In this post i'll use flexjson to produce a JSON output from my objects which can be customized.
I have the following classes in my domain model :
Product <-_has_many_-> Categorie
If you want to serialize the a product object with their categories. Just add the flexjson.jar to your webapp add this lines :
    @GET
    @Path("{id}")
    @ProduceMime("application/json")
    public String getJSONProduct(@PathParam("id") Integer id){
       return new JSONSerializer().exclude("*.class") // Exclude the property class from the json
                                  .include("categories")
                                  .serialize(product);
    }
See the output :
curl -v -H "Accept: application/json" http://localhost:8080/rest-json/resource/product/1
> Host: localhost:8080
> Accept: application/json
> 
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Mon, 21 Jul 2008 13:25:11 GMT
< 
* Connection #0 to host localhost left intact
* Closing connection #0
{"categories":[{"id":1,"name":"mobile"}],
"description":"A great mobile device !",
"id":1,"name":"iphone"}

lundi 7 juillet 2008

Jersey REST (JSR-311) and JSON

With Jersey it's very easy to create a RESTfull resource which returns an XML version of your objects:
    @GET
    @Path("{id}")
    @ProduceMime("application/xml")
    public Product getProduct(@PathParam("id") Integer id) {
        //...
        return product;
    }
Jersey uses JAXB to marshall the product object to XML. You can also customize the XML output with jaxb annotations.
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Product implements Serializable {
    
    @XmlAttribute
    private Integer Id;
    
    //...
}
This customization will produce an XML output that contains a Product element with an id attribute. Ok now, let's try the service :
curl -v -H "Accept: application/xml" http://localhost:8080/rest-json/resources/product/1
> GET /rest-json/resources/product HTTP/1.1
> User-Agent: curl/7.16.3 (powerpc-apple-darwin8.0) libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
> Host: localhost:8080
> Accept: application/xml
> 
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/xml
< Content-Length: 200
< Date: Wed, 09 Jul 2008 20:32:10 GMT
< 
* Connection #0 to host localhost left intact
* Closing connection #0
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product id="1">
 <name>myphone</name>
 <description>A great mobile device !</description>
 <category>
  <id>1</id>
  <name>mobile</name>
 </category>
</product>
To produce a JSON output just add the jettison-1.0-RC1.jar file from the jersey distribution to your webapp and change the mime type parameter of the @ProduceMime annotation like this :
    @GET
    @Path("{id}")
    @ProduceMime({"application/json","application/xml"})
    public Product getProduct(@PathParam("id") Integer id) {
        //...
        return product;
    }
You have now a REST resource which returns XML or JSON, the response format depends of the Accept header of the HTTP request :
curl -v -H "Accept: application/json" http://localhost:8080/rest-json/resources/product/1
> GET /rest-json/resources/product/1 HTTP/1.1
> User-Agent: curl/7.16.3 (powerpc-apple-darwin8.0) libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
> Host: localhost:8080
> Accept: application/json
> 
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Wed, 09 Jul 2008 20:47:26 GMT
< 
* Connection #0 to host localhost left intact
* Closing connection #0
{"product":{"@id":"1","name":"myphone",
"description":"A great mobile device !",
"category":{"id":"1","name":"mobile"}}}
It's very easy and powerful ! But if you look in details the JSON output you'll notice that the Product's property id is ""@id":"1"" and not ""id":1" ?!

In this case the Product object is marshalling by JAXB and converted to JSON by JETTISON. The returned JSON is mapped on the XML object representation.

If you want to produce a more simpler JSON like that :

{"product":{"id":1,"name":"myphone"}
You have to remove all JAXB annotations from the Product classe. You'll lose the XML customization and in some cases the XML output could be too verbose or not readable (for human ).
You can also use a JSON framework to manage the marshalling process ... see the part II :-)

vendredi 27 juin 2008

Tomcat : Add an Expires Header

One of the Yahoo bests practices for Speeding Up Your Web Site is to add an Expires or a Cache-Control Header.

If you use Tomcat as web-server (Tomcat 6 + NIO have good performances), you can use a Servlet filter to set a far future Expires header for static components like images.

Here an example of my servlet filter to set the Expires header :
public class HeaderFilter implements Filter {

    private static final Log log = LogFactory.getLog(HeaderFilter.class);
    
    private FilterConfig filterConfig;
    
    private Map<String, String> headersMap;

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;

        String headerParam = filterConfig.getInitParameter("header");
        if (headerParam == null) {
            log.warn("No headers were found in the web.xml (init-param) for the HeaderFilter !");
            return;
        }

        // Init the header list :
        headersMap = new LinkedHashMap();

        if (headerParam.contains("|")) {
            String[] headers = headerParam.split("|");
            for (String header : headers) {
                parseHeader(header);
            }

        } else {
            parseHeader(headerParam);
        }

        // Log configured headers .
        if (log.isInfoEnabled()) {
            log.info("The following headers were registered in the HeaderFilter :");
            Set<Entry<String, String>> headers = headersMap.entrySet();
            for (Entry<String, String> item : headers) {
                log.info(item.getKey() + ':' + item.getValue());
            }
        }
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (headersMap != null) {
            // Add the header to the response
            Set<Entry<String, String>> headers = headersMap.entrySet();
            for (Entry<String, String> header : headers) {
                ((HttpServletResponse) response).setHeader(header.getKey(), header.getValue());
            }
        }
        // Continue
        chain.doFilter(request, response);
    }

    public void destroy() {
        this.filterConfig = null;
        this.headersMap = null;
    }

    private void parseHeader(String header) {
        String headerName = header.substring(0, header.indexOf(":"));
        if (!headersMap.containsKey(headerName)) {
            headersMap.put(headerName, header.substring(header.indexOf(":") + 1));
        }
    }
}
To use this filter in your webapp, just edit your web.xml to add this lines :
    
        Set HTTP headers for a mapping.
        HeaderFilter
        juliusdev.filter.HeaderFilter
        
            Add an Expires Header
            header
            Expires: Thu, 15 Apr 2010 20:00:00 GMT
        
        
    
        HeaderFilter
        *.jpg
        REQUEST
    
This filter add the following Expires header to all jpg files, telling the browser that this response won't be stale until April 15, 2010. :
  • Expires: Thu, 15 Apr 2010 20:00:00 GMT
You can test the client side with FireFox plugins like FireBug and Yslow : I encourage you to read all the bests practices !.

jeudi 15 mai 2008

Jersey (JSR-311) + Hibernate

Jersey (0.7 EA) requires the 'asm-3.1.jar' to work but Hibernate core (3.2) already uses another version of that library : asm-attrs.jar, asm.jar and cglib-2.1.3.jar.

To fix this issue you have to download the no-dependencies version of cglib here and replace the cglib-2.1.3.jar file by the cglib-nodep-2.1_3.jar. After, you can remove the asm-attrs.jar, asm.jar jars and add the jersey.jar, jsr311-api.jar, asm-3.1.jar in your lib directory.

mardi 13 mai 2008

Java : Easy REST

A REST xml web service is a good candidate to deal with a RIA like a Flex client. But create a servlet that parses the request URI path is not an efficient way to build a REST resource. it's painful... Hey, Do you know the JSR-311 ? No ! it's the most easy way to create a REST resource, just adding some annotations to your methods, like this :
@GET
@ProduceMime("application/xml")
public Product getProduct(@PathParam("id")
Integer id) {
   // Get the product ...
   return product;
}
In this case the method must be invoke by a GET HTTP request, the response will have the content type 'application/xml' and returns some xml content. To invoke this web method you have to call the following URL :
  • http://mydomain/webcontext/resource_path/param1
  • Setup Jersey

    Jersey is the reference implementation of the JSR-311, the lastest stable release is provided with lots of examples. If you use netbeans 6.1 you can create and integrate a REST resource with Jersey easily. The core installation required only 3 jars :
  • jersey.jar, jsr311-api.jar, asm-3.1.jar
  • And you need to declare a servlet in your web.xml :
     
         ServletAdaptor
         com.sun.ws.rest.impl.container.servlet.ServletAdaptor
         1
     
     
         ServletAdaptor
         /resource/*
     
    
    Play with jersey, you'll love it :-)

    mardi 22 avril 2008

    Hibernate + mapping + Java enums collection.

    Hey, can you replace this select box by checks boxes in this web form ? Hum .... I have already mapped this combo box to an enum in my hibernate entity mapping. I must change that property to a set of enums (ManyToOne relation) :
    @Enumerated(EnumType.STRING)
    @Column(name="color")
    private ColorEnum color = ColorEnum.RED;
    
    to
    @CollectionOfElements
    @JoinTable( name="item_colors",joinColumns={@JoinColumn(name="item_id")}           )
    @Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
    @Enumerated(value=EnumType.STRING)
    @Column( name="color", length=25 )
    private Set<ColorEnum> colors = new LinkedHashSet<ColorEnum>();
    
    In the previous mapping the color property was mapped on a simple VARCHAR(25) of the item table. And in the second case i created a new table (MySQL) like this :
    CREATE TABLE `item_colors` (
    `item_id` INT NOT NULL,
    `color` VARCHAR(25) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
    PRIMARY KEY (`item_id`, `color`)
    )
    CHARACTER SET utf8;