001    /*
002     * Apache License
003     * Version 2.0, January 2004
004     * http://www.apache.org/licenses/
005     *
006     * Copyright 2008-2010 by chenillekit.org
007     *
008     * Licensed under the Apache License, Version 2.0 (the "License");
009     * you may not use this file except in compliance with the License.
010     * You may obtain a copy of the License at
011     *
012     * http://www.apache.org/licenses/LICENSE-2.0
013     */
014    
015    package org.chenillekit.tapestry.core.components;
016    
017    import java.awt.image.BufferedImage;
018    import java.io.ByteArrayInputStream;
019    import java.io.ByteArrayOutputStream;
020    import java.io.IOException;
021    import javax.imageio.ImageIO;
022    
023    import org.apache.commons.codec.DecoderException;
024    import org.apache.commons.codec.EncoderException;
025    import org.apache.commons.codec.net.BCodec;
026    import org.apache.tapestry5.BindingConstants;
027    import org.apache.tapestry5.ComponentResources;
028    import org.apache.tapestry5.Link;
029    import org.apache.tapestry5.MarkupWriter;
030    import org.apache.tapestry5.StreamResponse;
031    import org.apache.tapestry5.annotations.OnEvent;
032    import org.apache.tapestry5.annotations.Parameter;
033    import org.apache.tapestry5.annotations.Persist;
034    import org.apache.tapestry5.corelib.base.AbstractField;
035    import org.apache.tapestry5.ioc.annotations.Inject;
036    import org.apache.tapestry5.services.Request;
037    
038    import org.chenillekit.image.services.CaptchaProducer;
039    import org.chenillekit.tapestry.core.utils.JPEGInline;
040    
041    /**
042     * A Captcha is a type of challenge-response test used in computing to ensure that the response is not generated by a computer.
043     * The process usually involves one computer (a server) asking a user to complete a simple test which the computer is able to
044     * generate and grade. Because other computers are unable to solve the CAPTCHA, any user entering a correct solution is presumed
045     * to be human. Thus, it is sometimes described as a reverse Turing test, because it is administered by a machine and targeted to
046     * a human, in contrast to the standard Turing test that is typically administered by a human and targeted to a machine. A common
047     * type of CAPTCHA requires that the user type letters or digits from a distorted image that appears on the screen.
048     * <p/>
049     * This component based on <a href="http://code.google.com/p/kaptcha/">kaptcha library</a> and produce following HTML code:
050     * <p/>
051     * <pre>
052     * &lt;span id="kaptcha1" class="ck-kaptcha"&gt;
053     *   &lt;img id="kaptcha1_kaptcha" class="ck-kaptcha" src="..."/&gt;
054     *   &lt;input id="kaptcha1_input" class="ck-kaptcha" type="text" name="kaptcha1"/&gt;
055     * &lt;/span&gt;
056     * </pre>
057     * <p/>
058     * so you can change the design by cascading style sheets by the "ck-kaptcha" class.
059     * <br/>
060     * To use this component, you need the <a href="http://www.chenillekit.org/chenillekit-image/index.html">chenillekit-image library</a> in you classpath.
061     *
062     * @version $Id: Kaptcha.java 725 2010-11-03 19:40:03Z homburgs $
063     */
064    public class Kaptcha extends AbstractField
065    {
066            private static final String EVENT_NAME = "kaptchaEvent";
067    
068            @Parameter(required = true, defaultPrefix = BindingConstants.PROP)
069            private boolean value;
070    
071            @Persist
072            private String kaptchaValue;
073    
074            private String textFieldValue;
075    
076            /**
077             * ComponentResources. For blocks, messages, crete actionlink, trigger event
078             */
079            @Inject
080            private ComponentResources resources;
081    
082            /**
083             * Request object for information on current request
084             */
085            @Inject
086            private Request request;
087    
088            @Inject
089            private CaptchaProducer kaptchaProducer;
090    
091    
092            /**
093             * Tapestry render phase method.
094             * Start a tag here, end it in afterRender
095             */
096            void beginRender(MarkupWriter writer)
097            {
098                    writer.element("span",
099                                               "id", getClientId(),
100                                               "class", "ck-kaptcha");
101    
102                    writer.element("img",
103                                               "id", String.format("%s_kaptcha", getClientId()),
104                                               "src", getImageLink(),
105                                               "class", "ck-kaptcha");
106                    writer.end(); //  img
107    
108                    // <input t:id="textField" type="text" class="ck-kaptcha-input"/>
109    
110                    writer.element("input",
111                                               "id", String.format("%s_input", getClientId()),
112                                               "type", "text",
113                                               "name", getControlName(),
114                                               "value", textFieldValue,
115                                               "class", "ck-kaptcha");
116                    writer.end(); //  input
117            }
118    
119            /**
120             * Tapestry render phase method. End a tag here.
121             */
122            void afterRender(MarkupWriter writer)
123            {
124                    writer.end(); //  span
125            }
126    
127            private Link getImageLink()
128            {
129                    BCodec bCodec = new BCodec();
130                    try
131                    {
132                            return resources.createEventLink(EVENT_NAME, bCodec.encode(kaptchaProducer.createText()));
133                    }
134                    catch (EncoderException e)
135                    {
136                            throw new RuntimeException(e);
137                    }
138            }
139    
140            @OnEvent(value = EVENT_NAME)
141            public StreamResponse onKaptchaImage(String kaptchaValue)
142            {
143                    BCodec bCodec = new BCodec();
144                    try
145                    {
146                            this.kaptchaValue = bCodec.decode(kaptchaValue);
147                    }
148                    catch (DecoderException e)
149                    {
150                            throw new RuntimeException(e);
151                    }
152                    BufferedImage kapatchImage = kaptchaProducer.createImage(this.kaptchaValue);
153    
154                    try
155                    {
156                            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
157                            ImageIO.write(kapatchImage, "jpg", byteArrayOutputStream);
158                            ByteArrayInputStream bais = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
159                            return new JPEGInline(bais, (String[])null);
160                    }
161                    catch (IOException e)
162                    {
163                            throw new RuntimeException(e);
164                    }
165            }
166    
167            /**
168             * Method implemented by subclasses to actually do the work of processing the submission of the form. The element's
169             * elementName property will already have been set. This method is only invoked if the field is <strong>not {@link
170             * #isDisabled() disabled}</strong>.
171             *
172             * @param elementName the name of the element (used to find the correct parameter in the request)
173             */
174            protected void processSubmission(String elementName)
175            {
176                    String rawValue = request.getParameter(elementName);
177                    value = this.kaptchaValue.equals(rawValue);
178            }
179    }