/* @ITMillApache2LicenseForJavaFiles@ */ package com.vaadin.ui; import java.io.ByteArrayInputStream; import java.io.Serializable; import java.lang.reflect.Method; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import com.vaadin.Application; import com.vaadin.terminal.ApplicationResource; import com.vaadin.terminal.DownloadStream; import com.vaadin.terminal.ParameterHandler; import com.vaadin.terminal.URIHandler; import com.vaadin.terminal.gwt.client.ApplicationConnection; /** * 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. *

* Note, this is a new Ajax terminal specific component and is likely to change. * * @since 5.3 */ public class LoginForm extends CustomComponent { private Embedded iframe = new Embedded(); private ApplicationResource loginPage = new ApplicationResource() { public Application getApplication() { return LoginForm.this.getApplication(); } public int getBufferSize() { return getLoginHTML().length; } public long getCacheTime() { return -1; } public String getFilename() { return "login"; } public DownloadStream getStream() { return new DownloadStream(new ByteArrayInputStream(getLoginHTML()), getMIMEType(), getFilename()); } public String getMIMEType() { return "text/html; charset=utf-8"; } }; private ParameterHandler paramHandler = new ParameterHandler() { public void handleParameters(Map parameters) { if (parameters.containsKey("username")) { getWindow().addURIHandler(uriHandler); 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); } } }; private URIHandler uriHandler = new URIHandler() { private final String responce = "Login form handeled." + ""; public DownloadStream handleURI(URL context, String relativeUri) { if (relativeUri != null && relativeUri.contains("loginHandler")) { if (window != null) { window.removeURIHandler(this); } DownloadStream downloadStream = new DownloadStream( new ByteArrayInputStream(responce.getBytes()), "text/html", "loginSuccesfull"); downloadStream.setCacheTime(-1); return downloadStream; } else { return null; } } }; private Window window; 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() + getWindow().getName() + "/"; return ("\n" + "" + "" + "" + "

" + "" + "
" + "
Username
" + "
" + "
Password
" + "
" + "
Login
" + "") .getBytes(); } @Override public void attach() { super.attach(); getApplication().addResource(loginPage); getWindow().addParameterHandler(paramHandler); iframe.setSource(loginPage); } @Override public void detach() { getApplication().removeResource(loginPage); getWindow().removeParameterHandler(paramHandler); // store window temporary to properly remove uri handler once // response is handled. (May happen if login handler removes login // form window = getWindow(); if (window.getParent() != null) { window = window.getParent(); } 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, int 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, int unit) { super.setHeight(height, unit); if (iframe != null) { if (height < 0) { iframe.setHeight(UNDEFINED_HEIGHT); } else { iframe.setHeight("100%"); } } } }