001: /* Captcha.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Thu Mar 15 10:03:48 2007, Created by henrichen
010: }}IS_NOTE
011:
012: Copyright (C) 2007 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zul;
020:
021: import org.zkoss.zk.ui.Component;
022: import org.zkoss.zk.ui.Executions;
023: import org.zkoss.zk.ui.UiException;
024: import org.zkoss.zk.ui.event.Event;
025: import org.zkoss.zk.ui.event.Events;
026: import org.zkoss.zk.ui.event.EventListener;
027: import org.zkoss.zul.impl.CaptchaEngine;
028: import org.zkoss.image.AImage;
029: import org.zkoss.lang.Classes;
030: import org.zkoss.lang.Objects;
031: import org.zkoss.lang.Strings;
032:
033: import java.awt.Font;
034: import java.awt.Image;
035: import java.awt.Color;
036:
037: import java.util.Date;
038: import java.util.List;
039: import java.util.Random;
040: import java.util.ArrayList;
041:
042: /**
043: * The generic captcha component.
044: * @author henrichen
045: */
046: public class Captcha extends org.zkoss.zul.Image {
047: //control variable
048: private boolean _smartDrawCaptcha; //whether post the smartDraw event already?
049: private transient EventListener _smartDrawCaptchaListener; //the smartDrawListner
050:
051: private static Random _random = new Random();//random used for various operation
052: private static final String EXCLUDE = "0123456789IOilo"; //default exclude list
053: private static final int CHAR_START = '0'; //character start
054: private static final int CHAR_END = 'z'; //character end
055: private static final int CHAR_COUNT = CHAR_END - CHAR_START + 1; //charcater count
056: private static final Font[] DEFAULT_FONTS = new Font[] {
057: new Font("Arial", Font.BOLD, 35),
058: new Font("Courier", Font.BOLD, 35) };
059:
060: private int _intWidth = 200; //integer width in px
061: private int _intHeight = 50; //integer height in px
062:
063: private String _fontColor = "#404040"; //font color that used to draw text
064: private int _fontRGB = 0x404040; //font color in 0xRRGGBB
065:
066: private String _bgColor = "#74979B"; //background color in #RRGGBB form
067: private int _bgRGB = 0x74979B; //background color in 0xRRGGBB
068:
069: private List _fonts = new ArrayList(9); //fonts that can be used to draw text
070: private int _len = 5; //text length, default 5
071: private String _exclude = null;
072: private String _value; //captcha text value
073: private boolean _noise = true; //whether generate noise
074: private CaptchaEngine _engine; //the captcha engine that generate the distortion image.
075:
076: public Captcha() {
077: setWidth("200px");
078: setHeight("50px");
079: randomValue();
080: smartDrawCaptcha();
081: }
082:
083: /**
084: * Gets fonts list, default provide two fonts.
085: */
086: public List getFonts() {
087: return _fonts;
088: }
089:
090: /**
091: * Gets the default font list.
092: */
093: public Font[] getDefaultFonts() {
094: return DEFAULT_FONTS;
095: }
096:
097: /**
098: * Get nth Font.
099: */
100: public Font getFont(int j) {
101: if (_fonts.isEmpty()) {
102: return DEFAULT_FONTS[j];
103: }
104:
105: return (Font) _fonts.get(j);
106: }
107:
108: /**
109: * Add fonts into fonts list. If you did not add fonts, the default implementation
110: * would use the default fonts; i.e. bold Arial 35, and bold courier 35.
111: */
112: public void addFont(Font font) {
113: _fonts.add(font);
114: }
115:
116: /**
117: * Set font color.
118: */
119: public void setFontColor(String color) {
120: if (Objects.equals(color, _fontColor)) {
121: return;
122: }
123: _fontColor = color;
124: if (_fontColor == null) {
125: _fontRGB = 0;
126: } else {
127: _fontRGB = decode(_fontColor);
128: }
129: smartDrawCaptcha();
130: }
131:
132: /**
133: * Gets font color.
134: */
135: public String getFontColor() {
136: return _fontColor;
137: }
138:
139: /**
140: * Get the font color in int array (0: red, 1: green, 2:blue).
141: */
142: public int getFontRGB() {
143: return _fontRGB;
144: }
145:
146: /**
147: * Set the background color of the chart.
148: * @param color in #RRGGBB format (hexdecimal).
149: */
150: public void setBgColor(String color) {
151: if (Objects.equals(color, _bgColor)) {
152: return;
153: }
154: _bgColor = color;
155: if (_bgColor == null) {
156: _bgRGB = 0;
157: } else {
158: _bgRGB = decode(_bgColor);
159: }
160: smartDrawCaptcha();
161: }
162:
163: /**
164: * Get the background color of the captcha box (in string as #RRGGBB).
165: * null means default.
166: */
167: public String getBgColor() {
168: return _bgColor;
169: }
170:
171: /**
172: * Get the background color in int array (0: red, 1: green, 2:blue).
173: * null means default.
174: */
175: public int getBgRGB() {
176: return _bgRGB;
177: }
178:
179: /**
180: * Override super class to prepare the int width.
181: */
182: public void setWidth(String w) {
183: if (Objects.equals(w, getWidth())) {
184: return;
185: }
186: _intWidth = Chart.stringToInt(w);
187: super .setWidth(w);
188:
189: smartDrawCaptcha();
190: }
191:
192: /**
193: * Get the captcha int width in pixel; to be used by the derived subclass.
194: */
195: public int getIntWidth() {
196: return _intWidth;
197: }
198:
199: /**
200: * Override super class to prepare the int height.
201: */
202: public void setHeight(String h) {
203: if (Objects.equals(h, getHeight())) {
204: return;
205: }
206: _intHeight = Chart.stringToInt(h);
207: super .setHeight(h);
208:
209: smartDrawCaptcha();
210: }
211:
212: /**
213: * Get the captcha int height in pixel; to be used by the derived subclass.
214: */
215: public int getIntHeight() {
216: return _intHeight;
217: }
218:
219: /**
220: * Get the text value of this captcha.
221: */
222: public String getValue() {
223: return _value;
224: }
225:
226: /**
227: * Set the text value to be shown as the distortion captcha.
228: * @param text the captcha text value
229: */
230: public void setValue(String text) {
231: if (Objects.equals(text, _value)) {
232: return;
233: }
234: _value = text;
235:
236: smartDrawCaptcha();
237: }
238:
239: /** Set length of the autogenerated text value; default to 5.
240: */
241: public void setLength(int len) {
242: if (len == _len) {
243: return;
244: }
245: _len = len;
246:
247: randomValue();
248: smartDrawCaptcha();
249: }
250:
251: /** Get length of the autogenerated text value; default to 5.
252: */
253: public int getLength() {
254: return _len;
255: }
256:
257: /** Set exclude characters that will not be generated. Note that only digit and character is used
258: * in generating text value. If you leave exclude null, the default exclude list will be applied;
259: * i.e., 0123456789IilOo (only character (no digits) are used except I, i, l, O(big O), o(small o))
260: */
261: public void setExclude(String exclude) {
262: if (Objects.equals(_exclude, exclude))
263: return;
264:
265: _exclude = exclude;
266:
267: randomValue();
268: smartDrawCaptcha();
269: }
270:
271: /** Get exclude characters.
272: */
273: public String getExclude() {
274: return _exclude;
275: }
276:
277: /** Wheather generate noise; default to true.
278: */
279: public void setNoise(boolean b) {
280: _noise = b;
281: }
282:
283: /** Whether generate noise; default to true.
284: */
285: public boolean isNoise() {
286: return _noise;
287: }
288:
289: /**
290: * Regenerates new captcha text value and redraw.
291: */
292: public String randomValue() {
293: String exclude = _exclude == null ? EXCLUDE : _exclude;
294: int len = _len;
295:
296: final StringBuffer sb = new StringBuffer(len);
297: while (len > 0) {
298: final char c = (char) ('0' + _random.nextInt(CHAR_COUNT)); // ASCII '0' to 'z'
299: if (Character.isLetterOrDigit(c)
300: && exclude.indexOf((int) c) < 0) {
301: sb.append(c);
302: --len;
303: }
304: }
305: setValue(sb.toString());
306: return getValue();
307: }
308:
309: /** Sets the captcha engine by use of a class name.
310: * It creates an instance automatically.
311: */
312: public void setEngine(String clsnm) throws ClassNotFoundException,
313: NoSuchMethodException, IllegalAccessException,
314: InstantiationException,
315: java.lang.reflect.InvocationTargetException {
316: if (clsnm != null) {
317: setEngine((CaptchaEngine) Classes
318: .newInstanceByThread(clsnm));
319: }
320: }
321:
322: /**
323: * Set the captcha engine.
324: */
325: public void setEngine(CaptchaEngine engine) {
326: if (_engine != engine) {
327: _engine = engine;
328: }
329:
330: smartDrawCaptcha();
331: }
332:
333: /**
334: * Get the captcha engine.
335: *
336: * @exception UiException if failed to load the engine.
337: */
338: public CaptchaEngine getCaptchaEngine() throws UiException {
339: if (_engine == null)
340: _engine = newCaptchaEngine();
341: return _engine;
342: }
343:
344: /** Instantiates the default captcha engine.
345: * It is called, if {@link #setEngine} is not called with non-null
346: * engine.
347: *
348: * <p>By default, it looks up the component attribute called
349: * captcha-engine. If found, the value is assumed to be the class
350: * or the class name of the default engine (it must implement
351: * {@link CaptchaEngine}.
352: * If not found, {@link UiException} is thrown.
353: *
354: * <p>Derived class might override this method to provide your
355: * own default class.
356: *
357: * @exception UiException if failed to instantiate the engine
358: * @since 3.0.0
359: */
360: protected CaptchaEngine newCaptchaEngine() throws UiException {
361: Object v = getAttribute("captcha-engine");
362: if (v == null)
363: v = "org.zkoss.zkex.zul.impl.JHLabsCaptchaEngine";
364:
365: try {
366: final Class cls;
367: if (v instanceof String) {
368: cls = Classes.forNameByThread((String) v);
369: } else if (v instanceof Class) {
370: cls = (Class) v;
371: } else {
372: throw new UiException(
373: v != null ? "Unknown captcha-engine, " + v
374: : "The captcha-engine attribute is not defined");
375: }
376:
377: v = cls.newInstance();
378: } catch (Exception ex) {
379: throw UiException.Aide.wrap(ex);
380: }
381: if (!(v instanceof CaptchaEngine))
382: throw new UiException(CaptchaEngine.class
383: + " must be implemented by " + v);
384: return (CaptchaEngine) v;
385: }
386:
387: /**
388: * mark a draw flag to inform that this Chart needs update.
389: */
390: protected void smartDrawCaptcha() {
391: if (_smartDrawCaptcha) { //already mark smart draw
392: return;
393: }
394: _smartDrawCaptcha = true;
395: if (_smartDrawCaptchaListener == null) {
396: _smartDrawCaptchaListener = new EventListener() {
397: public void onEvent(Event event) {
398: if (Strings.isBlank(getValue()))
399: throw new UiException(
400: "captcha must specify text value");
401:
402: if (Strings.isBlank(getWidth()))
403: throw new UiException(
404: "captcha must specify width");
405:
406: if (Strings.isBlank(getHeight()))
407: throw new UiException(
408: "captcha must specify height");
409:
410: try {
411: //generate the distorted image based on the given text value
412: byte[] bytes = getCaptchaEngine()
413: .generateCaptcha(Captcha.this );
414: final AImage image = new AImage("captcha"
415: + new Date().getTime(), bytes);
416: setContent(image);
417: } catch (java.io.IOException ex) {
418: throw UiException.Aide.wrap(ex);
419: } finally {
420: _smartDrawCaptcha = false;
421: }
422: }
423: };
424: addEventListener("onSmartDrawCaptcha",
425: _smartDrawCaptchaListener);
426: }
427: Events.postEvent("onSmartDrawCaptcha", this , null);
428: }
429:
430: /*package*/static int decode(String color) {
431: if (color == null) {
432: return 0;
433: }
434: if (color.length() != 7 || !color.startsWith("#")) {
435: throw new UiException("Incorrect color format (#RRGGBB) : "
436: + color);
437: }
438: return Integer.parseInt(color.substring(1), 16);
439: }
440: }
|