JSF Central - Developing Unwired JSF Components
JSF Central

 
 Home 
 
 News 
 
 FAQ 
 
 Products 
 
 Articles & Books 
 
 Resources 
 
Developing Unwired JSF Components
(continued)
by Marcel Urbanek
20 Sep 2005 02:45 EDT

Learn how to develop JSF components that render for WAP and desktop browsers.


When programming a renderer, the first step is to get a response writer (context.getResponseWriter()), which will enable you to generate the output. The class ResponseWriter has methods like startElement(), endElement() and writeAttribute(), which enable us to generate standard HTML and XML tags (in this case an "input" tag).

Because the renderer itself contains no information about what to render (only how to render), the component that has to be rendered is handed over. We can determine which values are given in the JSP using methods like getDescription(), which are implemented in our component class. Because it is important to identify which form elements are ours, JSF generates a unique ID for every component (this is important when the application processes the user's request). When we use this ID we will be able to identify which values in the request are generated by us by invoking the getClientId(context) method.

In addition to the encodeEnd() method (which we overrode), there are also encodeBegin() and encodeChildren(). It sometimes makes more sense to use one of these methods instead of encodeEnd(), and in some cases all of them will be required.

After the encoding, the user will get the complete HTML page and may then click on our button, which will generate a request and invoke the decode() method. It is then possible to determine if someone has clicked the button. Because this method is the same in every renderer, this method is in the ButtonRenderer class, which all renderers extend.

public abstract class ButtonRenderer extends javax.faces.render.Renderer {

     public void decode(FacesContext context, UIComponent component) {
         Map parameter=context.getExternalContext().
                            getRequestParameterMap();
     
         if ((parameter.containsKey(component.getClientId(context)+".x")) ||
             (parameter.containsKey(component.getClientId(context)+".y")) ||
             (parameter.containsKey(component.getClientId(context))))
         {
                component.queueEvent(new ActionEvent(component));
         }
     }
}

The first step is to get the request map (context.getExternalContext().getRequestParameterMap()) and analyse it. If the request map contains our client ID, we know the button was clicked by the user, and so the action given in the JSP should be invoked using the component.queueEvent() method. Because our component extends javax.faces.render.Renderer, there are no further actions to take.

After this step, the component is ready (without the jpeg-button-renderer). You can download the complete source including a test page to try this component for yourself.

Those interested in online rendering of images should read the next section, which illustrates how to program the jpeg-button-renderer.

Generating images

Generating JPEG images is pretty simple because the familiar AWT and Swing classes can be used. The following method illustrates the implementation of the JPEG renderer after which some parts of the source code are explained. Note the comments in the source code.

public static String gen(Object session,String theme,boolean active,
			 String text) throws Exception
{
        String path;
        //Try to get the filesystem path of this application. This only 
        //works for portlet and servlet enivorenments.
        if (session instanceof PortletSession)
                path=((PortletSession)session).
                        getPortletContext().getRealPath("/");
        else if (session instanceof HttpSession)
                path=((HttpSession)session).getServletContext().
                	getRealPath("/");
        else
             throw new Exception(
                     "Unsupported enviroment (has to be portlet or servlet");
        path=path+"imguxjsf/";
        //The filename consists of all parameters that affect the layout of
        //the button. Having the same name means looking the same.
        String filename=text+theme+active;
        int tempenc;
        String filenameenc="";
        //Because of problems with the character set on some linux servers 
        //the string is converted in a string consisting of numbers.
        for (int enccounter=0;enccounter < filename.length();enccounter++)
        {
                tempenc=filename.charAt(enccounter);
                filenameenc+=""+tempenc;
        }
        //Because the filename may be pretty long, a hashcode is generated
        //that has always the same length.
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte digest[] = md.digest(filenameenc.getBytes());
        filename="";
        for (int i = 0; i < digest.length; i++)
                filename+=Integer.toHexString(digest[i]&0xff) ;
        //We check if the file already exists. If so, the name is simply
        //returned. If the file would have the same name it would look 
        //exactly the same, so there is no need to generate it again. The 
        //date is set for future optimisation.
        File file =new File(path+"temp/"+filename+".jpg");
        if (file.exists())
        {
                file.setLastModified(System.currentTimeMillis());
                return ("imguxjsf/temp/"+filename+".jpg");
        }
        //The properties file is opened which contains the layout 
        //information. How big the letters are and where the text starts 
        //for example.
        FileInputStream propInFile =
                new FileInputStream(path+theme+"/"+"gfx.properties");
        Properties properties = new Properties();
        properties.load(propInFile);
        String beginstr="begintab";
        String middlestr="middletab";
        String endstr="endtab";
        if (!active)
        {
                beginstr+="deact";
                middlestr+="deact";
                endstr+="deact";
        }
        beginstr+=".jpg";
        middlestr+=".jpg";
        endstr+=".jpg";
        //Now the imagefiles of the corresponding theme are loaded
        Image begin;
        Image middle;
        Image end;
        begin=new ImageIcon(path+theme+"/"+beginstr).getImage();
        middle=new ImageIcon(path+theme+"/"+middlestr,"").getImage();
        end=new ImageIcon(path+theme+"/"+endstr,"").getImage();
        int height= begin.getHeight(null);
        int width=50;
        int border= begin.getWidth(null);
        //A buffered image is produced to calculate the width of the text.
        BufferedImage ret=
                new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d=ret.createGraphics();
        //The fonts are loaded,either symbolic or normal truetype
        if ("true".equalsIgnoreCase(properties.getProperty("symbolicfont")))
        {
                f = Font.createFont(Font.PLAIN,
                                new FileInputStream(path+theme+"/font.ttf"));
        }
        else
        {
                f = Font.createFont(Font.TRUETYPE_FONT,
                                new FileInputStream(path+theme+"/font.ttf"));
        }
        //The size of the font is changed
        f=f.deriveFont(
                (float)Integer.parseInt((String)properties.get("fontsize")));
        //Calculate the width of the rendered text
        width=(int)f.getStringBounds(text,
                        g2d.getFontRenderContext()).getWidth();
        //Calculate the totalwidth
        width+=end.getWidth(null)+
                Integer.parseInt((String)properties.getProperty("fontposx"));
        //Generate a buffered image of the needed size
        ret=new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        g2d=ret.createGraphics();
        g2d.setFont(f);
        g2d.addRenderingHints(
                new RenderingHints(RenderingHints.KEY_ANTIALIASING,
                                    RenderingHints.VALUE_ANTIALIAS_ON));
        //Instantiate a mediatracker. A mediatracker makes sure, that a 
        //image is finished until we proceed to modify the picture or save 
        //it to disk.
        MediaTracker tracker = new MediaTracker(new Container()) ;
        g2d.drawImage(begin,0,0,begin.getWidth(null),begin.getHeight(null),
                        Color.BLACK,null);
        g2d.drawImage(middle,begin.getWidth(null),0,width-end.getWidth(null),
                        middle.getHeight(null),Color.BLACK,null);
        g2d.drawImage(end,width-end.getWidth(null),0,end.getWidth(null),
                        end.getHeight(null),Color.BLACK,null);
        //Add the image to the mediatracker and wait until the image is 
        //finished.
        tracker.addImage(ret,0);
        try{
                tracker.waitForAll();
        }
        catch (Exception e){;}
        String fontcolor;
        if (active)
        {
                fontcolor=properties.getProperty("activefontcolor");
        }
        else
        {
                fontcolor=properties.getProperty("deactivefontcolor");
        }
        StringTokenizer tokenizer=new StringTokenizer(fontcolor,",");
        int red=Integer.parseInt(tokenizer.nextToken());
        int green=Integer.parseInt(tokenizer.nextToken());
        int blue=Integer.parseInt(tokenizer.nextToken());
        Color color=new Color(red,green,blue);
        //The text is rendered.
        g2d.setColor(color);
        g2d.drawString(text,
              Integer.parseInt((String)properties.getProperty("fontposx")),
              Integer.parseInt((String)properties.getProperty("fontposy")));
        //Wait until the image is complete
        tracker.addImage(ret,0);
        try{
                tracker.waitForAll();
        }
        catch (Exception e){;}
        //Save jpeg to disk.
        FileOutputStream out=null;
        path=path.replace('\\','/');
        out=new FileOutputStream(path+"temp/"+filename+".jpg");
        JPEGImageEncoder  encoder = JPEGCodec.createJPEGEncoder(out);
        JPEGEncodeParam param=encoder.getDefaultJPEGEncodeParam(ret);
        param.setQuality((float)0.9, true);
        encoder.setJPEGEncodeParam(param);
        encoder.encode(ret);
        out.close();
        return ("imguxjsf/temp/"+filename+".jpg");
}

At the beginning of the method a hashcode is generated which contains all the information about the button to be rendered. If there is already a file that has the same name, the generation step is skipped because a button with the same properties has the same hashcode and looks the same.

If the filename doesn't exist, the button will be generated. The first step is to load the properties file (new FileInputStream(path + theme + "/" +"gfx.properties") ). Depending on the active parameter, the images are loaded and the images are combined to form a new button and the text is rendered. Before generating the JPEG file, it is important to make sure that the image is completely rendered, which can be done using a MediaTracker. The waitForAll() method waits exactly as long as it takes to render all pictures added to the MediaTracker, then the JPEG file can be generated after which the HTTP path to the file is returned to the calling method (our JSF renderer).

Please note, that if you use this component on Linux servers without a graphical user interface, you need to activate the headless mode of the java JRE.

Summary

In this article you learned that JSF can handle different output formats. You've learned how to use different renderers and a simple method to choose a renderer. And you have learned how to generate JPEGs on the server. This is a quite a powerful technique. You can render anything (i.e. charts) with the normal Swing and AWT classes and use it in your web applications. This may be extremely helpful when migrating a thick Java client to a web application.


Download: source code (ZIP; 2MB)


RSS feed(all feeds)

Inside Facelets
Learn how to use Facelets, an alternative to JSP.
In the Trenches
Read about real-world JSF projects!

Sponsored links

Site version 1.83  Report web site problems

Copyright (C) 2003-2007 Virtua, Inc. All Rights Reserved. Java, JavaServer Faces, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. Virtua, Inc. is independent of Sun Microsystems, Inc. All other trademarks are the sole property of their respective owners.