package cz.nkp.differ.gui.components; import com.vaadin.Application; import com.vaadin.data.validator.NullValidator; import com.vaadin.terminal.StreamResource; import com.vaadin.ui.CustomComponent; import com.vaadin.ui.Embedded; import com.vaadin.ui.Layout; import com.vaadin.ui.TextField; import com.vaadin.ui.VerticalLayout; import eu.livotov.tpt.TPTApplication; import eu.livotov.tpt.util.RandomPasswordGenerator; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.UUID; import javax.imageio.ImageIO; import org.apache.log4j.Logger; public class CaptchaComponent extends CustomComponent{ private static final long serialVersionUID = -1189704707163658035L; private static final int CAPTCHA_LENGTH = 5; public CaptchaComponent(){ this.setCompositionRoot(createComponent()); } private Layout createComponent(){ VerticalLayout layout = new VerticalLayout(); embed = new CaptchaEmbeddedImage(CAPTCHA_LENGTH); layout.addComponent(embed); captchaResponse = new TextField(); captchaResponse.addValidator(new NullValidator("You must provide a response to the validation!",false)); layout.addComponent(captchaResponse); return layout; } public boolean passedValidation(){ if(embed.verifyCaptchaCode((String)captchaResponse.getValue())){ return true; } return false; } public void reset(){ embed.generateCaptchaCode(CAPTCHA_LENGTH); captchaResponse.setValue(""); } private CaptchaEmbeddedImage embed; private TextField captchaResponse; } /** * This component represents a captcha image that can be displayed for some form validations. You * can provide your own text for captcha image or let the component to automatically generate it for * you. * * This is an adapted form of the TPTCaptcha, which does not function correctly on * non Oracle JVM. */ class CaptchaEmbeddedImage extends Embedded implements StreamResource.StreamSource { /** * */ private static final long serialVersionUID = 220231548210606723L; /** * Image provider for captcha */ private CaptchaImageProvider imageProvider = new DefaultCaptchaImageGenerator (); /** * Current captcha code */ private String captchaCode = ""; /** * Creates a captcha component with random 5-letter code generated */ public CaptchaEmbeddedImage () { super (); generateCaptchaCode ( 5 ); } /** * Creates a captcha component with random n-letter code generated */ public CaptchaEmbeddedImage (int length) { super (); if(length < 6){ length = 5; } generateCaptchaCode (length); } /** * Creates a captcha component with the specified text to be used as captcha code * * @param code captcha code */ public CaptchaEmbeddedImage ( String code ) { this (); setCaptchaCode ( code ); } /** * Sets the new captcha code. Image will be regenerated automatically. * * @param code new captcha code */ public void setCaptchaCode ( String code ) { setCaptchaCode ( code, TPTApplication.getCurrentApplication () ); } /** * Sets the new captcha code. Image will be regenerated automatically. Use this method * when you're using this widget without the using TPTApplication class. * @param code new code to generate * @param app Vaadin application instance. Required to generate a new StreamSource */ public void setCaptchaCode ( String code, Application app ) { captchaCode = code; refreshCaptchaImageSource ( app ); } /** * Provides the current captcha code that is displayed in the component * * @return current captcha code */ public String getCaptchaCode () { return captchaCode; } /** * Verifies the given code agains current captcha code. * * @param sample sample text to compare with the current captcha component * @return <code>true</code> if provided code matches the captcha (uses case-insensitive * comparison) */ public boolean verifyCaptchaCode ( final String sample ) { return sample != null && sample.equalsIgnoreCase ( getCaptchaCode () ); } /** * Generates and sets a random captcha code with the specified characters length. * * @param charactersCount number of characters in the new code * @return generated code. Note, that this method will also set the newly generated code to a * component. */ public String generateCaptchaCode ( int charactersCount ) { setCaptchaCode ( RandomPasswordGenerator.generate ( charactersCount ) ); return getCaptchaCode (); } /** * Sets the new image provider, that is responsible for creating captcha images. TPTCaptcha * compnent has its own default image generator, but you may specify your own implementation if * you wish. * * @param provider new image provider to use. Captcha code will be regenerated using this new * image provider immideately. */ public void setCaptchaImageProvider ( CaptchaImageProvider provider ) { setCaptchaImageProvider ( provider, TPTApplication.getCurrentApplication () ); } /** * Sets the new image provider, that is responsible for creating captcha images. TPTCaptcha * compnent has its own default image generator, but you may specify your own implementation if * you wish. * * @param provider new image provider to use. Captcha code will be regenerated using this new * image provider immideately. */ public void setCaptchaImageProvider ( CaptchaImageProvider provider, Application app) { this.imageProvider = provider; refreshCaptchaImageSource ( app ); } public InputStream getStream () { BufferedImage image = imageProvider.getCaptchaImage ( "" + captchaCode ); ByteArrayOutputStream bos = new ByteArrayOutputStream (); try { ImageIO.write(image, "jpg", bos); } catch (IOException e) { Logger.getLogger(getClass()).error("Captcha IO write from bytestream produced an error",e); } return new ByteArrayInputStream ( bos.toByteArray () ); } private void refreshCaptchaImageSource ( Application app ) { setSource ( new StreamResource ( this, UUID.randomUUID ().toString () + ".jpg", app ) ); } /** * API for connecting custom image generators. Use setImageProvider method of the TPTCaptcha * component to set the new image provider if you want the captcha images to be generated * differently. */ public interface CaptchaImageProvider { /** * Should provide a BufferedImage that represents the capctha code. * * @param text text to encode in the image * @return encoded captcha image to be displayed */ BufferedImage getCaptchaImage ( String text ); } private class DefaultCaptchaImageGenerator implements CaptchaEmbeddedImage.CaptchaImageProvider { private static final int LETTER_WIDTH = 50; private static final int IMAGE_HEIGHT = 60; private static final double SKEW = 2.5; private static final int DRAW_LINES = 4; private static final int DRAW_BOXES = 1; private final Color[] RANDOM_BG_COLORS = { Color.RED, Color.CYAN, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.YELLOW }; private final Color[] RANDOM_FG_COLORS = { Color.BLACK, Color.BLUE, Color.DARK_GRAY }; public BufferedImage getCaptchaImage ( String code ) { int MAX_LETTER_COUNT = code.length (); int MAX_X = LETTER_WIDTH * MAX_LETTER_COUNT; int MAX_Y = IMAGE_HEIGHT; BufferedImage outImage = new BufferedImage ( MAX_X, MAX_Y, BufferedImage.TYPE_INT_RGB ); Graphics2D g2d = outImage.createGraphics (); g2d.setColor ( java.awt.Color.WHITE ); g2d.fillRect ( 0, 0, MAX_X, MAX_Y ); for ( int i = 0; i < DRAW_BOXES; i++ ) { paindBoxes ( g2d, MAX_X, MAX_Y ); } Font font = new Font ( "dialog", 1, 33 ); g2d.setFont ( font ); g2d.setColor ( Color.BLACK ); g2d.drawRect ( 0, 0, ( MAX_X ) - 1, MAX_Y - 1 ); AffineTransform affineTransform = new AffineTransform (); for ( int i = 0; i < MAX_LETTER_COUNT; i++ ) { double angle = 0; if ( Math.random () * 2 > 1 ) { angle = Math.random () * SKEW; } else { angle = Math.random () * -SKEW; } affineTransform.rotate ( angle, ( LETTER_WIDTH * i ) + ( LETTER_WIDTH / 2 ), MAX_Y / 2 ); g2d.setTransform ( affineTransform ); setRandomFont ( g2d ); setRandomFGColor ( g2d ); g2d.drawString ( code.substring ( i, i + 1 ), ( i * LETTER_WIDTH ) + 3, 28 + ( int ) ( Math.random () * 6 ) ); affineTransform.rotate ( -angle, ( LETTER_WIDTH * i ) + ( LETTER_WIDTH / 2 ), MAX_Y / 2 ); } g2d.setXORMode ( Color.RED ); g2d.setStroke ( new BasicStroke ( 1 ) ); g2d.drawLine ( 0, 0, MAX_X, MAX_Y ); g2d.setXORMode ( Color.YELLOW ); g2d.drawLine ( 0, MAX_Y, MAX_X, 0 ); for ( int i = 0; i < DRAW_LINES; i++ ) { g2d.setXORMode ( Color.RED ); g2d.setStroke ( new BasicStroke ( 2 ) ); int y1 = ( int ) ( Math.random () * MAX_Y ); g2d.drawLine ( 0, y1, MAX_X, y1 ); } return outImage; } private void paindBoxes ( Graphics2D g2d, int MAX_X, int MAX_Y ) { int colorId = ( int ) ( Math.random () * RANDOM_BG_COLORS.length ); g2d.setColor ( RANDOM_BG_COLORS[colorId] ); g2d.fillRect ( getRandomX ( MAX_X ), getRandomY ( MAX_Y ), getRandomX ( MAX_X ), getRandomY ( MAX_Y ) ); } private int getRandomX ( int max_x ) { return ( int ) ( Math.random () * max_x ); } private int getRandomY ( int max_y ) { return ( int ) ( Math.random () * max_y ); } private void setRandomFont ( Graphics2D g2d ) { Font font = new Font ( "dialog", 1, 33 ); g2d.setFont ( font ); } private void setRandomFGColor ( Graphics2D g2d ) { int colorId = ( int ) ( Math.random () * RANDOM_FG_COLORS.length ); g2d.setColor ( RANDOM_FG_COLORS[colorId] ); } } }