fr.exanpe.t5.lib.components.SecurePassword.java Source code

Java tutorial

Introduction

Here is the source code for fr.exanpe.t5.lib.components.SecurePassword.java

Source

//
// Copyright 2011 EXANPE <exanpe@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package fr.exanpe.t5.lib.components;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.tapestry5.Asset;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.ClientElement;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.AfterRender;
import org.apache.tapestry5.annotations.BeginRender;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.annotations.SetupRender;
import org.apache.tapestry5.annotations.SupportsInformalParameters;
import org.apache.tapestry5.dom.Element;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;

import fr.exanpe.t5.lib.constants.SecurePasswordEventTypeEnum;
import fr.exanpe.t5.lib.services.ExanpeComponentService;

/**
 * Display a panel allowing a user to enter a password by clicking or hovering numbers.<br/>
 * This component must be enclosed into a <t:form> component.<br/>
 * JavaScript : This component is bound to a class Exanpe.SecurePassword.<br/>
 * CSS : This component is bound to a class exanpe-spwd.<br/>
 * 
 * @author jmaupoux
 */
@Import(library = { "${exanpe.yui2-base}/yahoo-dom-event/yahoo-dom-event.js",
        "js/exanpe-t5-lib.js" }, stylesheet = { "css/exanpe-t5-lib-core.css", "css/exanpe-t5-lib-skin.css" })
@SupportsInformalParameters
public class SecurePassword implements ClientElement {

    /**
     * Defines the width of the grid where are randomly positioned the numbers (column number).
     */
    @Property
    @Parameter(defaultPrefix = BindingConstants.LITERAL, required = true, allowNull = false, value = "4")
    private int gridWidth;

    /**
     * Defines the height of the grid where are randomly positioned the numbers (row number).
     */
    @Property
    @Parameter(defaultPrefix = BindingConstants.LITERAL, required = true, allowNull = false, value = "4")
    private int gridHeight;

    /**
     * Defines the max length of the input password field.
     */
    @Property
    @Parameter(defaultPrefix = BindingConstants.LITERAL, required = false)
    @SuppressWarnings("unused")
    private Integer maxlength;

    /**
     * Defines the set of characters to display in the password grid.
     */
    @Property
    @Parameter(defaultPrefix = BindingConstants.LITERAL, required = true, allowNull = false, value = "0123456789")
    private String characters;

    /**
     * Defines the event triggering the number.
     * 
     * @see SecurePasswordEventTypeEnum
     */
    @Property
    @Parameter(defaultPrefix = BindingConstants.LITERAL, required = true, allowNull = false, value = "click")
    private SecurePasswordEventTypeEnum eventType;

    /**
     * Defines the asset displayed to clear the password field.
     */
    @Property
    @Parameter(defaultPrefix = BindingConstants.ASSET, required = true, allowNull = false, value = "img/spwd/reset.png")
    @SuppressWarnings("unused")
    private Asset resetImage;

    /**
     * Defined the name of the value to set the password in the page.
     */
    @Property
    @Parameter(autoconnect = true)
    @SuppressWarnings("unused")
    private String value;

    @Property
    private String uniqueId;

    /**
     * Allow to iterate through a dynamic number of elements, as 1..${} fails in t:loop
     */
    @Property
    private java.util.List<String> dummyRowList;

    /**
     * Allow to iterate through a dynamic number of elements, as 1..${} fails in t:loop
     */
    @Property
    private java.util.List<String> dummyColumnList;

    /**
     * the index of the current row
     */
    @Property
    private int gridRowIndex;

    /**
     * the index of the current column
     */
    @Property
    private int gridColumnIndex;

    private static final String ROOT_CSS_CLASS = "exanpe-spwd";

    @Inject
    private JavaScriptSupport javaScriptSupport;

    @Inject
    private ComponentResources resources;

    @Inject
    private ExanpeComponentService ecservice;

    /**
     * Stores as instant access a case id and its value associated
     * Key = id de la case
     * Value = valeur de binding
     */
    private Map<String, String> positionningMap;

    /**
     * Stores the content of each case in reading order (left to right, then up to down)
     */
    private List<String> positionning;

    @SetupRender
    void init() {
        uniqueId = javaScriptSupport.allocateClientId(resources);

        // Build the positionning list, to position items on the grid
        positionning = new LinkedList<String>(Arrays.asList(characters.split("")));
        positionning.remove(0);

        int gridNumber = gridWidth * gridHeight;

        for (int i = characters.length(); i < gridNumber; i++)
            positionning.add("");

        Collections.shuffle(positionning);

        // Build a dummy list to iterate over
        dummyRowList = new ArrayList<String>(gridHeight);
        for (int i = 0; i < gridHeight; ++i)
            dummyRowList.add("");

        dummyColumnList = new ArrayList<String>(gridWidth);
        for (int i = 0; i < gridWidth; ++i)
            dummyColumnList.add("");

        positionningMap = new HashMap<String, String>();
    }

    @BeginRender
    void begin(MarkupWriter writer) {
        Element e = writer.element("div");

        e.attribute("id", getClientId());
        resources.renderInformalParameters(writer);

        ecservice.reorderCSSClassDeclaration(e, ROOT_CSS_CLASS);
    }

    @AfterRender
    void end(MarkupWriter writer) {
        writer.end();

        JSONObject data = buildJSONData();

        javaScriptSupport.addInitializerCall("securePasswordBuilder", data);
    }

    private JSONObject buildJSONData() {
        JSONObject positionningMapJSON = new JSONObject();

        Iterator<Map.Entry<String, String>> ite = positionningMap.entrySet().iterator();

        while (ite.hasNext()) {
            Map.Entry<String, String> next = ite.next();
            positionningMapJSON.accumulate(next.getKey(), next.getValue());
        }

        JSONObject data = new JSONObject();
        data.accumulate("id", getClientId());
        data.accumulate("eventType", eventType.toString().toLowerCase());
        data.accumulate("positionnings", positionningMapJSON);

        return data;
    }

    /**
     * Return true if a case is empty
     * 
     * @return true if a case is empty
     */
    public boolean isCaseEmpty() {
        return StringUtils.isEmpty(positionningMap.get(getClientId() + gridRowIndex + gridColumnIndex));
    }

    /**
     * Return the value of the current case, or an empty String if no character is in the case
     * 
     * @return the value of the current case
     */
    public String getCaseValue() {
        return positionning.get(gridRowIndex * gridWidth + gridColumnIndex);
    }

    /**
     * Return the id of a case
     * 
     * @return the id of the current case
     */
    public String getCaseId() {
        String id = getClientId() + gridRowIndex + gridColumnIndex;
        positionningMap.put(id, getCaseValue());
        return id;
    }

    public String getClientId() {
        return uniqueId;
    }
}