/* * Copyright 2011 Vaadin Ltd. * * 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 com.vaadin.ui; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import com.vaadin.Application; import com.vaadin.shared.ApplicationConstants; import com.vaadin.terminal.ApplicationResource; import com.vaadin.terminal.DownloadStream; import com.vaadin.terminal.RequestHandler; import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.WrappedResponse; /** * LoginForm is a Vaadin component to handle common problem among Ajax * applications: browsers password managers don't fill dynamically created forms * like all those UI elements created by Vaadin. *

* For developer it is easy to use: add component to a desired place in you UI * and add LoginListener to validate form input. Behind the curtain LoginForm * creates an iframe with static html that browsers detect. *

* Login form is by default 100% width and height, so consider using it inside a * sized {@link Panel} or {@link Window}. *

* Login page html can be overridden by replacing protected getLoginHTML method. * As the login page is actually an iframe, styles must be handled manually. By * default component tries to guess the right place for theme css. * * @since 5.3 */ public class LoginForm extends CustomComponent { private String usernameCaption = "Username"; private String passwordCaption = "Password"; private String loginButtonCaption = "Login"; private Embedded iframe = new Embedded(); private ApplicationResource loginPage = new ApplicationResource() { @Override public Application getApplication() { return LoginForm.this.getApplication(); } @Override public int getBufferSize() { return getLoginHTML().length; } @Override public long getCacheTime() { return -1; } @Override public String getFilename() { return "login"; } @Override public DownloadStream getStream() { return new DownloadStream(new ByteArrayInputStream(getLoginHTML()), getMIMEType(), getFilename()); } @Override public String getMIMEType() { return "text/html; charset=utf-8"; } }; private final RequestHandler requestHandler = new RequestHandler() { @Override public boolean handleRequest(Application application, WrappedRequest request, WrappedResponse response) throws IOException { String requestPathInfo = request.getRequestPathInfo(); if ("/loginHandler".equals(requestPathInfo)) { // Ensure UI.getCurrent() works in listeners UI.setCurrent(getUI()); response.setCacheTime(-1); response.setContentType("text/html; charset=utf-8"); response.getWriter() .write("Login form handled." + ""); Map parameters = request.getParameterMap(); HashMap params = new HashMap(); // expecting single params for (Iterator it = parameters.keySet().iterator(); it .hasNext();) { String key = it.next(); String value = (parameters.get(key))[0]; params.put(key, value); } LoginEvent event = new LoginEvent(params); fireEvent(event); return true; } return false; } }; public LoginForm() { iframe.setType(Embedded.TYPE_BROWSER); iframe.setSizeFull(); setSizeFull(); setCompositionRoot(iframe); addStyleName("v-loginform"); } /** * Returns byte array containing login page html. If you need to override * the login html, use the default html as basis. Login page sets its target * with javascript. * * @return byte array containing login page html */ protected byte[] getLoginHTML() { String appUri = getApplication().getURL().toString(); try { return ("\n" + "" + "" + "" + "

" + "" + "
" + "
" + usernameCaption + "
" + "
" + "
" + passwordCaption + "
" + "
" + "
" + loginButtonCaption + "
" + "") .getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8 encoding not avalable", e); } } @Override public void attach() { super.attach(); getApplication().addResource(loginPage); getApplication().addRequestHandler(requestHandler); iframe.setSource(loginPage); } @Override public void detach() { getApplication().removeResource(loginPage); getApplication().removeRequestHandler(requestHandler); super.detach(); } /** * This event is sent when login form is submitted. */ public class LoginEvent extends Event { private Map params; private LoginEvent(Map params) { super(LoginForm.this); this.params = params; } /** * Access method to form values by field names. * * @param name * @return value in given field */ public String getLoginParameter(String name) { if (params.containsKey(name)) { return params.get(name); } else { return null; } } } /** * Login listener is a class capable to listen LoginEvents sent from * LoginBox */ public interface LoginListener extends Serializable { /** * This method is fired on each login form post. * * @param event */ public void onLogin(LoginForm.LoginEvent event); } private static final Method ON_LOGIN_METHOD; private static final String UNDEFINED_HEIGHT = "140px"; private static final String UNDEFINED_WIDTH = "200px"; static { try { ON_LOGIN_METHOD = LoginListener.class.getDeclaredMethod("onLogin", new Class[] { LoginEvent.class }); } catch (final java.lang.NoSuchMethodException e) { // This should never happen throw new java.lang.RuntimeException( "Internal error finding methods in LoginForm"); } } /** * Adds LoginListener to handle login logic * * @param listener */ public void addListener(LoginListener listener) { addListener(LoginEvent.class, listener, ON_LOGIN_METHOD); } /** * Removes LoginListener * * @param listener */ public void removeListener(LoginListener listener) { removeListener(LoginEvent.class, listener, ON_LOGIN_METHOD); } @Override public void setWidth(float width, Unit unit) { super.setWidth(width, unit); if (iframe != null) { if (width < 0) { iframe.setWidth(UNDEFINED_WIDTH); } else { iframe.setWidth("100%"); } } } @Override public void setHeight(float height, Unit unit) { super.setHeight(height, unit); if (iframe != null) { if (height < 0) { iframe.setHeight(UNDEFINED_HEIGHT); } else { iframe.setHeight("100%"); } } } /** * Returns the caption for the user name field. * * @return String */ public String getUsernameCaption() { return usernameCaption; } /** * Sets the caption to show for the user name field. The caption cannot be * changed after the form has been shown to the user. * * @param usernameCaption */ public void setUsernameCaption(String usernameCaption) { this.usernameCaption = usernameCaption; } /** * Returns the caption for the password field. * * @return String */ public String getPasswordCaption() { return passwordCaption; } /** * Sets the caption to show for the password field. The caption cannot be * changed after the form has been shown to the user. * * @param passwordCaption */ public void setPasswordCaption(String passwordCaption) { this.passwordCaption = passwordCaption; } /** * Returns the caption for the login button. * * @return String */ public String getLoginButtonCaption() { return loginButtonCaption; } /** * Sets the caption (button text) to show for the login button. The caption * cannot be changed after the form has been shown to the user. * * @param loginButtonCaption */ public void setLoginButtonCaption(String loginButtonCaption) { this.loginButtonCaption = loginButtonCaption; } }